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 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <errno.h>
33
34 #include <signal.h>
35 #include <time.h>
36 #include <libxml/xmlerror.h>
37 #include <sys/types.h>
38 #include <cx/map.h>
39 #include <cx/string.h>
40 #include <cx/utils.h>
41 #include <cx/list.h>
42 #include <cx/hash_map.h>
43 #include <cx/printf.h>
44
45 #ifndef _WIN32
46
47 #include <unistd.h>
48 #include <utime.h>
49 #include <pthread.h>
50 #else
51
52
53 #endif
54
55
56 #include <math.h>
57
58 #include <libidav/webdav.h>
59 #include <libidav/utils.h>
60 #include <libidav/crypto.h>
61
62 #include <libidav/session.h>
63
64 #include "sync.h"
65
66 #include "config.h"
67 #include "sopt.h"
68 #include "error.h"
69 #include "assistant.h"
70 #include "libxattr.h"
71 #include "tags.h"
72
73 #include "system.h"
74
75
76 #include <ctype.h>
77
78 #ifdef _WIN32
79 #define strcasecmp _stricmp
80
81 #ifndef S_ISDIR
82 #define S_ISDIR(mode) ((mode) &
_S_IFMT) ==
_S_IFDIR
83 #define S_ISREG(mode) ((mode) &
_S_IFMT) ==
_S_IFREG
84 #endif
85
86 #endif
87
88 static DavContext *ctx;
89
90 static int sync_shutdown =
0;
91
92 static FILE *synclog;
93
94 static int orig_argc;
95 static char **orig_argv;
96
97 static void xmlerrorfnc(
void * c,
const char * msg, ... ) {
98 va_list ap;
99 va_start(ap, msg);
100 vfprintf(stderr, msg, ap);
101 va_end(ap);
102 }
103
104 static DavPropName defprops[] = {
105 {
"DAV:",
"getetag" },
106 {
DAV_NS,
"status" },
107 {
DAV_NS,
"content-hash" },
108 {
DAV_NS,
"split" },
109 {
DAV_PROPS_NS,
"finfo" },
110 {
DAV_PROPS_NS,
"tags" },
111 {
DAV_PROPS_NS,
"xattributes" },
112 {
DAV_PROPS_NS,
"link" }
113 };
114 static size_t numdefprops =
8 ;
115
116 void log_printf(
const char *fmt, ...) {
117 va_list ap;
118 va_start(ap, fmt);
119 cxmutstr str = cx_vasprintf(fmt, ap);
120
121 printf(
"%s", str.ptr);
122 if(synclog) {
123 fprintf(synclog,
"%s", str.ptr);
124 }
125 free(str.ptr);
126
127 va_end(ap);
128 }
129
130 void log_error(
const char *fmt, ...) {
131 va_list ap;
132 va_start(ap, fmt);
133 cxmutstr str = cx_vasprintf(fmt, ap);
134
135 fprintf(stderr,
"%s", str.ptr);
136 if(synclog) {
137 fprintf(synclog,
"%s", str.ptr);
138 }
139 free(str.ptr);
140
141 va_end(ap);
142 }
143
144 void log_resource_error(DavSession *sn,
const char *path) {
145 print_resource_error(sn, path);
146 if(synclog) {
147 print_resource_error_to_file(synclog, sn, path);
148 }
149 }
150
151
152 int logfile_open(SyncDirectory *dir) {
153 int ret =
0;
154 if(dir && dir->logfile) {
155 char *lf_path = dir->logfile;
156 char *lf_path_fr =
NULL;
157 if(dir->logfile[
0] !=
'/') {
158 lf_path = config_file_path(dir->logfile);
159 lf_path_fr = lf_path;
160 }
161
162 synclog = fopen(lf_path,
"a");
163 if(!synclog) {
164 fprintf(stderr,
"Cannot open logfile %s: %s\n", lf_path, strerror(errno));
165 ret =
1;
166 }
else {
167 time_t t = time(
NULL);
168 char *now = ctime(&t);
169 size_t len = strlen(now);
170 if(now[len-
1] ==
'\n') {
171 len--;
172 }
173
174 fprintf(synclog,
"[%.*s] ", (
int)len, now);
175 for(
int i=
0;i<orig_argc;i++) {
176 fprintf(synclog,
"%s ", orig_argv[i]);
177 }
178 fprintf(synclog,
"\n");
179 }
180 if(lf_path_fr) {
181 free(lf_path_fr);
182 }
183 }
184 return ret;
185 }
186
187
188
189
190 static int nullstrcmp(
const char *s1,
const char *s2) {
191 if(!s1 && s2) {
192 return -
1;
193 }
194 if(s1 && !s2) {
195 return 1;
196 }
197 if(!s1 && !s2) {
198 return 0;
199 }
200 return strcmp(s1, s2);
201 }
202
203 static char* nullstrdup(
const char *s) {
204 return s ? strdup(s) :
NULL;
205 }
206
207 static void nullfree(
void *p) {
208 if(p) {
209 free(p);
210 }
211 }
212
213
214
215 static CxIterator mapIteratorValues(CxMap *map) {
216 return cxMapIteratorValues(map ? map : cxEmptyMap);
217 }
218
219 static CxIterator listIterator(CxList *list) {
220 return cxListIterator(list ? list : cxEmptyList);
221 }
222
223 typedef void*(*clonefunc)(
void *elm,
void *userdata);
224
225 static CxMap* mapClone(
const CxAllocator *a, CxMap *map, clonefunc clone,
void *userdata) {
226 CxMap *newmap = cxHashMapCreate(a, map->store_pointer ?
CX_STORE_POINTERS : map->item_size, map->size +
4);
227
228 CxIterator i = cxMapIterator(map);
229 if(clone) {
230 cx_foreach(CxMapEntry*, entry, i) {
231 void *newdata = clone(entry->value, userdata);
232 cxMapPut(newmap, *entry->key, newdata);
233 }
234 }
else {
235 cx_foreach(CxMapEntry*, entry, i) {
236 cxMapPut(newmap, *entry->key, entry->value);
237 }
238 }
239
240 return newmap;
241 }
242
243 int dav_sync_main(
int argc,
char **argv);
244
245 #ifdef _WIN32
246 static char* wchar2utf8(
const wchar_t *wstr,
size_t wlen) {
247 size_t maxlen = wlen *
4;
248 char *ret = malloc(maxlen +
1);
249 int ret_len = WideCharToMultiByte(
250 CP_UTF8,
251 0,
252 wstr,
253 wlen,
254 ret,
255 maxlen,
256 NULL,
257 NULL);
258 ret[ret_len] =
0;
259 return ret;
260 }
261
262 int wmain(
int argc,
wchar_t **argv) {
263 char **argv_utf8 = calloc(argc,
sizeof(
char*));
264 for(
int i=
0;i<argc;i++) {
265 argv_utf8[i] = wchar2utf8(argv[i], wcslen(argv[i]));
266 }
267
268 int ret = dav_sync_main(argc, argv_utf8);
269
270 for(
int i=
0;i<argc;i++) {
271 free(argv_utf8[i]);
272 }
273 free(argv_utf8);
274
275 return ret;
276 }
277 #else
278 int main(
int argc,
char **argv) {
279 return dav_sync_main(argc, argv);
280 }
281 #endif
282
283 int dav_sync_main(
int argc,
char **argv) {
284 orig_argc = argc;
285 orig_argv = argv;
286
287 if(argc <
2) {
288 fprintf(stderr,
"Missing command\n");
289 print_usage(argv[
0]);
290 return -
1;
291 }
292
293 char *cmd = argv[
1];
294 CmdArgs *args = cmd_parse_args(argc -
2, argv +
2);
295 if(!args) {
296 print_usage(argv[
0]);
297 return -
1;
298 }
299 int ret =
EXIT_FAILURE;
300
301 if(!strcasecmp(cmd,
"version") || !strcasecmp(cmd,
"-version")
302 || !strcasecmp(cmd,
"--version")) {
303 fprintf(stderr,
"dav-sync %s\n",
DAV_VERSION);
304 cmd_args_free(args);
305 return -
1;
306 }
307
308 xmlGenericErrorFunc fnc = xmlerrorfnc;
309 initGenericErrorDefaultFunc(&fnc);
310 sys_init();
311 ctx = dav_context_new();
312 int cfgret = load_config(ctx) || load_sync_config();
313
314
315
316 #ifndef _WIN32
317 struct sigaction act;
318 memset(&act,
0,
sizeof(
struct sigaction));
319 act.sa_handler =
SIG_IGN;
320 sigaction(
SIGPIPE, &act,
NULL);
321
322
323 pthread_mutex_t mutex =
PTHREAD_MUTEX_INITIALIZER;
324 pthread_mutex_lock(&mutex);
325 pthread_t tid;
326 #else
327 int tid;
328 int mutex;
329 #endif
330
331 if(!strcmp(cmd,
"check") || !strcmp(cmd,
"check-config")) {
332 if(!cfgret) {
333 fprintf(stdout,
"Configuration OK.\n");
334 ret =
EXIT_SUCCESS;
335 }
else {
336
337 ret =
EXIT_FAILURE;
338 }
339 }
else if(!cfgret) {
340 if(!strcmp(cmd,
"pull")) {
341 tid = start_sighandler(&mutex);
342 ret = cmd_pull(args,
FALSE);
343 stop_sighandler(&mutex, tid);
344 }
else if(!strcmp(cmd,
"push")) {
345 tid = start_sighandler(&mutex);
346 ret = cmd_push(args,
FALSE,
FALSE);
347 stop_sighandler(&mutex, tid);
348 }
else if(!strcmp(cmd,
"outgoing")) {
349 ret = cmd_push(args,
TRUE,
FALSE);
350 }
else if(!strcmp(cmd,
"archive")) {
351 tid = start_sighandler(&mutex);
352 ret = cmd_push(args,
FALSE,
TRUE);
353 stop_sighandler(&mutex, tid);
354 }
else if(!strcmp(cmd,
"restore")) {
355 tid = start_sighandler(&mutex);
356 ret = cmd_restore(args);
357 stop_sighandler(&mutex, tid);
358 }
else if(!strcmp(cmd,
"list-conflicts")) {
359 ret = cmd_list_conflicts(args);
360 }
else if(!strcmp(cmd,
"resolve-conflicts")) {
361 ret = cmd_resolve_conflicts(args);
362 }
else if(!strcmp(cmd,
"delete-conflicts")) {
363 ret = cmd_delete_conflicts(args);
364 }
else if(!strcmp(cmd,
"list-versions")) {
365 ret = cmd_list_versions(args);
366 }
else if(!strcmp(cmd,
"trash-info")) {
367 ret = cmd_trash_info(args);
368 }
else if(!strcmp(cmd,
"empty-trash")) {
369 ret = cmd_empty_trash(args);
370 }
else if(!strcmp(cmd,
"add-tag")) {
371 ret = cmd_add_tag(args);
372 }
else if(!strcmp(cmd,
"remove-tag")) {
373 ret = cmd_remove_tag(args);
374 }
else if(!strcmp(cmd,
"set-tags")) {
375 ret = cmd_set_tags(args);
376 }
else if(!strcmp(cmd,
"list-tags")) {
377 ret = cmd_list_tags(args);
378 }
else if(!strcmp(cmd,
"add-dir")
379 || !strcmp(cmd,
"add-directory")) {
380 ret = cmd_add_directory(args);
381 }
else if(!strcmp(cmd,
"list-dirs")
382 || !strcmp(cmd,
"list-directories")) {
383 ret = cmd_list_dirs();
384 }
else if(!strcmp(cmd,
"check-repos")
385 || !strcmp(cmd,
"check-repositories")) {
386 ret = cmd_check_repositories(args);
387 }
else {
388 print_usage(argv[
0]);
389 }
390 }
391
392
393 cmd_args_free(args);
394 dav_context_destroy(ctx);
395
396 free_config();
397 free_sync_config();
398
399 curl_global_cleanup();
400 xmlCleanupParser();
401
402 sys_uninit();
403
404 return ret;
405 }
406
407 void print_usage(
char *cmd) {
408 fprintf(stderr,
"Usage: %s command [options] arguments...\n\n", cmd);
409
410 fprintf(stderr,
"Commands:\n");
411 fprintf(stderr,
" pull [-cldr] [-t <tags>] <directory>\n");
412 fprintf(stderr,
" push [-cldrSRM] [-t <tags>] <directory>\n");
413 fprintf(stderr,
" archive [-cldSRM] [-t <tags>] <directory>\n");
414 fprintf(stderr,
415 " restore [-ldRM] [-V <version>] [-s <directory>] [file...]\n");
416 fprintf(stderr,
" list-conflicts <directory>\n");
417 fprintf(stderr,
" resolve-conflicts <directory>\n");
418 fprintf(stderr,
" delete-conflicts <directory>\n");
419 fprintf(stderr,
" trash-info <directory>\n");
420 fprintf(stderr,
" empty-trash <directory>\n");
421 fprintf(stderr,
" list-versions [-s <syncdir>] <file>\n");
422 fprintf(stderr,
" add-tag [-s <syncdir>] <file> <tag>\n");
423 fprintf(stderr,
" remove-tag [-s <syncdir>] <file> <tag>\n");
424 fprintf(stderr,
" set-tags [-s <syncdir>] <file> [tags]\n");
425 fprintf(stderr,
" list-tags [-s <syncdir>] <file>\n\n");
426
427 fprintf(stderr,
"Options:\n");
428 fprintf(stderr,
" -c Disable conflict detection\n");
429 fprintf(stderr,
" -l Lock the repository before access\n");
430 fprintf(stderr,
" -d Don''t lock the repository\n");
431 fprintf(stderr,
" -t <tags> "
432 "Only sync files which have the specified tags\n");
433 fprintf(stderr,
" -r "
434 "Remove resources not matching the tag filter\n");
435 fprintf(stderr,
" -V <vers> Restore specific version\n");
436 fprintf(stderr,
" -S Save previous file version\n");
437 fprintf(stderr,
" -R Restore removed files\n");
438 fprintf(stderr,
" -M Restore modified files\n");
439 fprintf(stderr,
" -s <syncdir> Name of the syncdir the file is in\n");
440 fprintf(stderr,
" -v Verbose output (all commands)\n\n");
441
442 fprintf(stderr,
"Config commands:\n");
443 fprintf(stderr,
" add-directory\n");
444 fprintf(stderr,
" list-directories\n");
445 fprintf(stderr,
" check-config\n");
446 fprintf(stderr,
" check-repositories\n\n");
447 }
448
449 static void handlesig(
int sig) {
450 if(sync_shutdown) {
451 exit(-
1);
452 }
453 fprintf(stderr,
"abort\n");
454 sync_shutdown =
1;
455 }
456
457 #ifndef _WIN32
458 static void* sighandler(
void *data) {
459 signal(
SIGTERM, handlesig);
460 signal(
SIGINT, handlesig);
461
462 pthread_mutex_t *mutex = data;
463 pthread_mutex_lock(mutex);
464 return NULL;
465 }
466
467 pthread_t start_sighandler(
pthread_mutex_t *mutex) {
468 pthread_t tid;
469 if(pthread_create(&tid,
NULL, sighandler, mutex)) {
470 perror(
"pthread_create");
471 exit(-
1);
472 }
473 return tid;
474 }
475
476 void stop_sighandler(
pthread_mutex_t *mutex,
pthread_t tid) {
477 pthread_mutex_unlock(mutex);
478 void *data;
479 pthread_join(tid, &data);
480 }
481 #else
482
483 int start_sighandler(
int* mutex) {
484 return 0;
485 }
486 int stop_sighandler(
int* mutex,
int tid) {
487 return 0;
488 }
489
490 #endif
491
492 static char* create_local_path(SyncDirectory *dir,
const char *path) {
493 char *local_path = util_concat_path(dir->path, path);
494 size_t local_path_len = strlen(local_path);
495 if(local_path[local_path_len-
1] ==
'/') {
496 local_path[local_path_len-
1] =
'\0';
497 }
498 return local_path;
499 }
500
501 static int res_matches_filter(Filter *filter,
char *res_path) {
502
503 CxIterator i = cxListIterator(filter->include);
504 cx_foreach(
regex_t*, pattern, i) {
505 if (regexec(pattern, res_path,
0,
NULL,
0) ==
0) {
506 CxIterator e = cxListIterator(filter->exclude);
507 cx_foreach(
regex_t*, expat, e) {
508 if (regexec(expat, res_path,
0,
NULL,
0) ==
0) {
509 return 1;
510 }
511 }
512 return 0;
513 }
514 }
515 return 1;
516 }
517
518 static int res_matches_dir_filter(SyncDirectory *dir,
char *res_path) {
519
520 if (dir->trash) {
521 cxmutstr rpath = cx_mutstr(util_concat_path(dir->path, res_path));
522 if (util_path_isrelated(dir->trash, rpath.ptr)) {
523 free(rpath.ptr);
524 return 1;
525 }
526 free(rpath.ptr);
527 }
528
529
530 if (dir->versioning) {
531 if(util_path_isrelated(dir->versioning->collection, res_path)) {
532 return 1;
533 }
534 }
535
536 return res_matches_filter(&dir->filter, res_path);
537 }
538
539 static int res_matches_tags(DavResource *res, SyncTagFilter *tagfilter) {
540 if(!tagfilter || tagfilter->mode ==
DAV_SYNC_TAGFILTER_OFF) {
541 return 1;
542 }
543
544
545
546
547
548
549
550 if(res->iscollection) {
551 return 1;
552 }
553
554 DavXmlNode *tagsprop = dav_get_property_ns(res,
DAV_PROPS_NS,
"tags");
555 CxList *res_tags = parse_dav_xml_taglist(tagsprop);
556
557 int ret = matches_tagfilter(res_tags, tagfilter);
558
559 cxListDestroy(res_tags);
560
561 return ret;
562 }
563
564 static int localres_matches_tags(
565 SyncDirectory *dir,
566 LocalResource *res,
567 SyncTagFilter *tagfilter)
568 {
569 if(!tagfilter || tagfilter->mode ==
DAV_SYNC_TAGFILTER_OFF) {
570 return 1;
571 }
572
573
574
575
576
577
578 if(res->isdirectory) {
579 return 1;
580 }
581
582 DavBool changed =
0;
583 CxList *res_tags = sync_get_file_tags(dir, res, &changed,
NULL);
584 if(!res_tags) {
585 res_tags = cxEmptyList;
586 }
587
588 int ret = matches_tagfilter(res_tags, tagfilter);
589 cxListDestroy(res_tags);
590 return ret;
591 }
592
593 static int localres_cmp_path(LocalResource *a, LocalResource *b,
void *n) {
594 return strcmp(a->path, b->path);
595 }
596
597 static int localres_cmp_path_desc(LocalResource *a, LocalResource *b,
void *n) {
598 return -strcmp(a->path, b->path);
599 }
600
601 static DavSession* create_session(CmdArgs *a, DavContext *ctx, DavCfgRepository *repo,
char *collection) {
602 int flags = dav_repository_get_flags(repo);
603 DavBool find_collection =
TRUE;
604 if((flags &
DAV_SESSION_DECRYPT_NAME) !=
DAV_SESSION_DECRYPT_NAME) {
605 char *url = util_concat_path(repo->url.value.ptr, collection);
606 dav_repository_set_url(get_config(), repo, cx_str(url));
607 free(url);
608 collection =
NULL;
609 find_collection =
FALSE;
610 }
611 if(!collection || (collection[
0] ==
'/' && strlen(collection) ==
1)) {
612
613
614
615 find_collection =
FALSE;
616 }
617
618 DavSession *sn = connect_to_repo(ctx, repo, collection, request_auth, a);
619
620 sn->flags = flags;
621 sn->key = dav_context_get_key(ctx, repo->default_key.value.ptr);
622 curl_easy_setopt(sn->handle,
CURLOPT_HTTPAUTH, repo->authmethods);
623 curl_easy_setopt(sn->handle,
CURLOPT_SSLVERSION, repo->ssl_version);
624 if(repo->cert.value.ptr) {
625 curl_easy_setopt(sn->handle,
CURLOPT_CAPATH, repo->cert.value.ptr);
626 }
627 if(!repo->verification.value) {
628 curl_easy_setopt(sn->handle,
CURLOPT_SSL_VERIFYPEER,
0);
629 curl_easy_setopt(sn->handle,
CURLOPT_SSL_VERIFYHOST,
0);
630 }
631
632 if(find_collection) {
633 DavResource *col = dav_resource_new(sn, collection);
634 dav_exists(col);
635
636
637
638 char *newurl = util_concat_path(repo->url.value.ptr, util_resource_name(col->href));
639 dav_session_set_baseurl(sn, newurl);
640 free(newurl);
641 }
642
643 return sn;
644 }
645
646 static void print_allowed_cmds(SyncDirectory *dir) {
647 fprintf(stderr,
"Allowed commands: ");
648 char *sep =
"";
649 if((dir->allow_cmd &
SYNC_CMD_PULL) ==
SYNC_CMD_PULL) {
650 fprintf(stderr,
"pull");
651 sep =
", ";
652 }
653 if((dir->allow_cmd &
SYNC_CMD_PUSH) ==
SYNC_CMD_PUSH) {
654 fprintf(stderr,
"%spush", sep);
655 sep =
", ";
656 }
657 if((dir->allow_cmd &
SYNC_CMD_ARCHIVE) ==
SYNC_CMD_ARCHIVE) {
658 fprintf(stderr,
"%sarchive", sep);
659 }
660 fprintf(stderr,
"\n");
661 }
662
663 static void localres_keep(SyncDatabase *db,
const char *path) {
664 LocalResource *local = cxMapRemoveAndGet(db->resources, cx_hash_key_str(path));
665 if(local) {
666 local->keep =
TRUE;
667 }
668 }
669
670 static int xattr_filter(
const char *name, SyncDirectory *dir) {
671
672 if(
673 dir->tagconfig &&
674 dir->tagconfig->store ==
TAG_STORE_XATTR &&
675 !strcmp(dir->tagconfig->xattr_name, name))
676 {
677 return 0;
678 }
679 return 1;
680 }
681
682 void res2map(DavResource *root, CxMap *map) {
683 CxList *stack = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
684 cxListInsert(stack,
0, root->children);
685 while(stack->size >
0) {
686 DavResource *res = cxListAt(stack,
0);
687 cxListRemove(stack,
0);
688
689 while(res) {
690 cxMapPut(map, cx_hash_key_str(res->path), res);
691
692 if(res->children) {
693 cxListInsert(stack,
0, res->children);
694 }
695 res = res->next;
696 }
697 }
698 cxListDestroy(stack);
699 }
700
701 static CxHashKey resource_path_key(DavResource *res) {
702 CxHashKey key = {
NULL,
0,
0 };
703 if(res && res->path) {
704 cxstring res_path = cx_str(res->path);
705 if(res_path.length >
0 && res_path.ptr[res_path.length-
1] ==
'/') {
706 res_path.length--;
707 }
708 key = cx_hash_key(res_path.ptr, res_path.length);
709 }
710 return key;
711 }
712
713 int cmd_pull(CmdArgs *a, DavBool incoming) {
714 if(a->argc !=
1) {
715 fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
716 return -
1;
717 }
718
719 SyncTagFilter* tagfilter = parse_tagfilter_string(
720 cmd_getoption(a,
"tags"),
DAV_SYNC_TAGFILTER_SCOPE_RESOURCE);
721 if (!tagfilter) {
722 fprintf(stderr,
"Malformed tag filter\n");
723 return -
1;
724 }
725
726
727
728 SyncDirectory *dir = scfg_get_dir(a->argv[
0]);
729 if(!dir) {
730 fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
731 return -
1;
732 }
733 if(scfg_check_dir(dir)) {
734 return -
1;
735 }
736 if(logfile_open(dir)) {
737 return -
1;
738 }
739
740 if((dir->allow_cmd &
SYNC_CMD_PULL) !=
SYNC_CMD_PULL) {
741 fprintf(stderr,
"Command ''pull'' is not allowed for this sync dir\n");
742 print_allowed_cmds(dir);
743 return -
1;
744 }
745
746 DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
747 if(!repo) {
748 fprintf(stderr,
"Unknown repository %s\n", dir->repository);
749 return -
1;
750 }
751
752 SyncDatabase *db = load_db(dir->database);
753 if(!db) {
754 fprintf(stderr,
"Cannot load database file: %s\n", dir->database);
755 return -
1;
756 }
757 remove_deleted_conflicts(dir, db);
758
759 CxMap *hashes =
NULL;
760 if(
SYNC_HASHING(dir)) {
761 hashes = create_hash_index(db);
762 }
763
764 DavSession *sn = create_session(a, ctx, repo, dir->collection);
765 cxMempoolRegister(sn->mp, db, (cx_destructor_func)destroy_db);
766 if (cmd_getoption(a,
"verbose")) {
767 curl_easy_setopt(sn->handle,
CURLOPT_VERBOSE,
1L);
768 curl_easy_setopt(sn->handle,
CURLOPT_STDERR, stderr);
769 sn->logfunc = dav_verbose_log;
770 }
771
772
773 char *locktokenfile =
NULL;
774 DavBool locked =
FALSE;
775 DavResource *root = dav_resource_new(sn,
"/");
776 root->iscollection =
TRUE;
777 if((dir->lockpush || cmd_getoption(a,
"lock")) && !cmd_getoption(a,
"nolock")) {
778 if(
dav_lock_t(root, dir->lock_timeout)) {
779 log_resource_error(sn,
"/");
780 dav_session_destroy(sn);
781 log_error(
"Abort\n");
782 return -
1;
783 }
784 DavLock *lock = dav_get_lock(sn,
"/");
785 if(lock) {
786 log_printf(
"Lock-Token: %s\n", lock->token);
787 }
788 locked =
TRUE;
789 locktokenfile = create_locktoken_file(dir->name, lock->token);
790 }
791
792 int ret =
0;
793 DavResource *ls = dav_query(sn,
"select D:getetag,idav:split,idav:status,`idav:content-hash`,idavprops:tags,idavprops:finfo,idavprops:xattributes,idavprops:link from / with depth = infinity");
794 if(!ls) {
795 log_resource_error(sn,
"/");
796 if(locked) {
797 if(dav_unlock(root)) {
798 log_resource_error(sn,
"/");
799 }
else {
800 locked =
FALSE;
801 }
802 }
803
804 log_error(
"Abort\n");
805
806 dav_session_destroy(sn);
807
808 return -
1;
809 }
810 if(!ls->iscollection) {
811 fprintf(stderr,
"%s is not a collection.\nAbort.\n", ls->path);
812 if(locked) {
813 if(dav_unlock(root)) {
814 log_resource_error(sn,
"/");
815 }
else {
816 locked =
FALSE;
817 }
818 }
819
820 dav_session_destroy(sn);
821
822 if(!locked && locktokenfile) {
823 remove(locktokenfile);
824 }
825
826 return -
1;
827 }
828
829 DavBool remove_file = cmd_getoption(a,
"remove") ?
1 :
0;
830
831 int sync_success =
0;
832 int sync_delete =
0;
833 int sync_error =
0;
834 int sync_conflict =
0;
835
836 CxList *res_modified = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
837 CxList *res_new = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
838 CxList *res_moved = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
839 CxList *res_link = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
840 CxList *res_conflict = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
841 CxList *res_mkdir = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
842 CxList *res_metadata = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
843 CxList *res_broken = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
844 CxMap *lres_removed = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
16);
845
846
847 CxMap *dbres = mapClone(cxDefaultAllocator, db->resources,
NULL,
NULL);
848
849 CxList *stack = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
850 cxListInsert(stack,
0, ls->children);
851 while(stack->size >
0) {
852 DavResource *res = cxListAt(stack,
0);
853 cxListRemove(stack,
0);
854
855 while(res) {
856 DavBool res_filtered =
FALSE;
857 if (res_matches_dir_filter(dir, res->path)) {
858 res_filtered =
TRUE;
859 }
else {
860 CxIterator iter = cxListIterator(dir->filter.tags);
861 cx_foreach(SyncTagFilter *, tf, iter) {
862 if(!res_matches_tags(res, tf)) {
863 res_filtered =
TRUE;
864 break;
865 }
866 }
867 }
868 if(res_filtered) {
869
870 localres_keep(db, res->path);
871 res = res->next;
872 continue;
873 }
874
875 if (!res_matches_tags(res, tagfilter)) {
876 if(!remove_file) {
877 localres_keep(db, res->path);
878 }
879 res = res->next;
880 continue;
881 }
882
883 char *status = dav_get_string_property(res,
"idav:status");
884 if(status && !strcmp(status,
"broken")) {
885 localres_keep(db, res->path);
886 cxListAdd(res_broken, res);
887 res = res->next;
888 continue;
889 }
890
891
892 int change = resource_get_remote_change(a, res, dir, db);
893 switch(change) {
894 case REMOTE_NO_CHANGE:
break;
895 case REMOTE_CHANGE_MODIFIED: {
896 cxListAdd(res_modified, res);
897 break;
898 }
899 case REMOTE_CHANGE_NEW: {
900 cxListAdd(res_new, res);
901 break;
902 }
903 case REMOTE_CHANGE_DELETED:
break;
904 case REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED: {
905 cxListAdd(res_conflict, res);
906 break;
907 }
908 case REMOTE_CHANGE_METADATA: {
909 cxListAdd(res_metadata, res);
910 break;
911 }
912 case REMOTE_CHANGE_MKDIR: {
913 cxListAdd(res_mkdir, res);
914 break;
915 }
916 case REMOTE_CHANGE_LINK: {
917 cxListAdd(res_link, res);
918 break;
919 }
920 }
921
922
923
924
925 cxMapRemove(dbres, resource_path_key(res));
926
927 if(!dav_get_property_ns(res,
DAV_NS,
"split") && res->children) {
928 cxListInsert(stack,
0, res->children);
929 }
930 res = res->next;
931 }
932 }
933
934
935
936
937 CxIterator i = mapIteratorValues(dbres);
938 cx_foreach(LocalResource *, local, i) {
939 if (res_matches_dir_filter(dir, local->path)) {
940 continue;
941 }
942 if(!local->keep) {
943 cxMapPut(lres_removed, cx_hash_key_str(local->path), local);
944 if(lres_removed->size > lres_removed->size *
2) {
945 cxMapRehash(lres_removed);
946 }
947 }
948 }
949
950
951
952
953
954
955 i = cxListIterator(res_mkdir);
956 cx_foreach(DavResource *, res, i) {
957 if(sync_get_collection(a, dir, res, db)) {
958 sync_error++;
959 }
960 }
961
962
963 CxMap *conflicts = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, res_conflict->size+
16);
964 i = cxListIterator(res_conflict);
965 cx_foreach(DavResource *, res, i) {
966 cxMapPut(conflicts, cx_hash_key_str(res->path), res);
967 }
968
969 if(
SYNC_HASHING(dir)) {
970
971 SYS_STAT s;
972 CxMutIterator mut_iter = cxListMutIterator(res_new);
973 cx_foreach(DavResource *, res, mut_iter) {
974 if(dav_get_property_ns(res,
DAV_PROPS_NS,
"link")) {
975 continue;
976 }
977
978 char *hash = sync_get_content_hash(res);
979 if(!hash) {
980 continue;
981 }
982
983 LocalResource *local = cxMapGet(hashes, cx_hash_key_str(hash));
984 if(!local) {
985 continue;
986 }
987
988 char *local_path = util_concat_path(dir->path, local_resource_path(local));
989 int staterr = sys_stat(local_path, &s);
990 free(local_path);
991 if(staterr) {
992
993 continue;
994 }
995
996 MovedFile *mf = malloc(
sizeof(MovedFile));
997 mf->content = local;
998 mf->resource = res;
999 if(cxMapRemoveAndGet(lres_removed, cx_hash_key_str(local->path))) {
1000 mf->copy =
FALSE;
1001 }
else {
1002 mf->copy =
TRUE;
1003 }
1004
1005 cxListAdd(res_moved, mf);
1006
1007
1008 cxIteratorFlagRemoval(mut_iter);
1009 }
1010 }
1011
1012
1013 i = cxListIterator(res_moved);
1014 cx_foreach(MovedFile *, mf, i) {
1015 if(sync_shutdown) {
1016 break;
1017 }
1018
1019 DavBool issplit = dav_get_property_ns(mf->resource,
DAV_NS,
"split") ?
1 :
0;
1020 if(cxMapGet(conflicts, cx_hash_key_str(mf->resource->path))) {
1021 rename_conflict_file(dir, db, mf->resource->path, issplit);
1022 sync_conflict++;
1023 }
1024
1025
1026 if(sync_move_resource(a, dir, mf->resource, mf->content, mf->copy, db, &sync_success)) {
1027 fprintf(stderr,
"%s failed: %s\n", mf->copy?
"copy":
"move", mf->resource->path);
1028 sync_error++;
1029 }
1030 }
1031
1032
1033 for(
int n=
0;n<
4;n++) {
1034 CxList *ls;
1035 if(n ==
0) {
1036 ls = res_new;
1037 }
else if(n ==
1) {
1038 ls = res_modified;
1039 }
else if(n ==
2) {
1040 ls = res_conflict;
1041 }
else {
1042 ls = res_link;
1043 }
1044 CxIterator iter = cxListIterator(ls);
1045 cx_foreach(DavResource *, res, iter) {
1046 if(sync_shutdown) {
1047 break;
1048 }
1049
1050 DavBool issplit = dav_get_property_ns(res,
DAV_NS,
"split") ?
1 :
0;
1051 if(cxMapGet(conflicts, cx_hash_key_str(res->path))) {
1052 rename_conflict_file(dir, db, res->path, issplit);
1053 sync_conflict++;
1054 }
1055
1056
1057 if(sync_get_resource(a, dir, res->path, res, db,
TRUE, &sync_success)) {
1058 fprintf(stderr,
"resource download failed: %s\n", res->path);
1059 sync_error++;
1060 }
1061 }
1062 }
1063
1064
1065 i = cxListIterator(res_metadata);
1066 cx_foreach(DavResource *, res, i) {
1067 if(sync_shutdown) {
1068 break;
1069 }
1070
1071 LocalResource *local = cxMapGet(db->resources, resource_path_key(res));
1072 if(local) {
1073 log_printf(
"update: %s\n", res->path);
1074 char *res_path = resource_local_path(res);
1075 char *local_path = create_local_path(dir, res->path);
1076 free(res_path);
1077 if(sync_store_metadata(dir, local_path, local, res)) {
1078 fprintf(stderr,
"Metadata update failed: %s\n", res->path);
1079 sync_error++;
1080 }
else {
1081 SYS_STAT s;
1082 if(sys_stat(local_path, &s)) {
1083 fprintf(stderr,
"Cannot stat file after update: %s\n", strerror(errno));
1084 }
1085 sync_set_metadata_from_stat(local, &s);
1086 sync_success++;
1087 }
1088 free(local_path);
1089 }
else {
1090
1091 fprintf(stderr,
1092 "Cannot update metadata of file %s: not in database\n",
1093 res->path);
1094 }
1095 }
1096
1097 CxList *rmdirs = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_pathlen_cmp,
CX_STORE_POINTERS);
1098 i = cxMapIteratorValues(lres_removed);
1099 cx_foreach(LocalResource *, removed_res, i) {
1100 if(sync_shutdown) {
1101 break;
1102 }
1103
1104 int ret = sync_remove_local_resource(dir, removed_res);
1105 if(ret == -
1) {
1106 cxListAdd(rmdirs, removed_res);
1107 }
else if(ret ==
0) {
1108 LocalResource *local = cxMapRemoveAndGet(db->resources, cx_hash_key_str(removed_res->path));
1109 if(local) {
1110 local_resource_free(local);
1111 }
1112 sync_delete++;
1113 }
1114 }
1115 cxMapDestroy(lres_removed);
1116
1117
1118 cxListSort(rmdirs);
1119
1120 i = cxListIterator(rmdirs);
1121 cx_foreach(LocalResource *, local_dir, i) {
1122 if(!sync_remove_local_directory(dir, local_dir)) {
1123
1124 LocalResource *local = cxMapRemoveAndGet(db->resources, cx_hash_key_str(local_dir->path));
1125 if(local) {
1126 local_resource_free(local);
1127 }
1128 sync_delete++;
1129 }
1130 }
1131
1132
1133 if(locked) {
1134 if(dav_unlock(root)) {
1135 log_resource_error(sn,
"/");
1136 ret = -
1;
1137 }
else {
1138 locked =
FALSE;
1139 }
1140 }
1141
1142
1143 if(store_db(db, dir->database, dir->db_settings)) {
1144 fprintf(stderr,
"Cannot store sync db\n");
1145 ret = -
2;
1146 }
1147
1148
1149 dav_session_destroy(sn);
1150
1151 if(!locked && locktokenfile) {
1152 remove(locktokenfile);
1153 }
1154
1155
1156 if(ret != -
2) {
1157 char *str_success = sync_success ==
1 ?
"file" :
"files";
1158 char *str_delete = sync_delete ==
1 ?
"file" :
"files";
1159 char *str_error = sync_error ==
1 ?
"error" :
"errors";
1160 char *str_conflict = sync_conflict ==
1 ?
"conflict" :
"conflicts";
1161 log_printf(
"Result: %d %s pulled, %d %s deleted, %d %s, %d %s\n",
1162 sync_success, str_success,
1163 sync_delete,str_delete,
1164 sync_conflict, str_conflict,
1165 sync_error, str_error);
1166 }
1167
1168 return ret;
1169 }
1170
1171 RemoteChangeType resource_get_remote_change(
1172 CmdArgs *a,
1173 DavResource *res,
1174 SyncDirectory *dir,
1175 SyncDatabase *db)
1176 {
1177 DavBool update_db =
FALSE;
1178
1179 char *etag = dav_get_string_property(res,
"D:getetag");
1180 if(!etag) {
1181 fprintf(stderr,
"Error: resource %s has no etag\n", res->path);
1182 return REMOTE_NO_CHANGE;
1183 }
1184 char *hash = sync_get_content_hash(res);
1185
1186 DavBool issplit = dav_get_property(res,
"idav:split") ?
TRUE :
FALSE;
1187 if(issplit) {
1188 util_remove_trailing_pathseparator(res->path);
1189 }
1190 DavBool iscollection = res->iscollection && !issplit;
1191
1192 RemoteChangeType type = cmd_getoption(a,
"conflict") ?
1193 REMOTE_CHANGE_MODIFIED :
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
1194
1195 LocalResource *local = cxMapGet(db->resources, resource_path_key(res));
1196 char *local_path = create_local_path(dir, res->path);
1197
1198 char *link =
SYNC_SYMLINK(dir) ?
1199 dav_get_string_property_ns(res,
DAV_PROPS_NS,
"link") :
NULL;
1200
1201 SYS_STAT s;
1202 DavBool exists =
1;
1203 if(sys_stat(local_path, &s)) {
1204 if(errno !=
ENOENT) {
1205 fprintf(stderr,
"Cannot stat file: %s\n", local_path);
1206 free(local_path);
1207 return REMOTE_NO_CHANGE;
1208 }
1209 exists =
0;
1210 }
1211
1212 RemoteChangeType ret =
REMOTE_NO_CHANGE;
1213 if(iscollection) {
1214 if(!exists) {
1215 ret =
REMOTE_CHANGE_MKDIR;
1216 }
else if(local &&
S_ISDIR(s.st_mode)) {
1217 local->isdirectory =
1;
1218 }
else {
1219
1220 ret =
REMOTE_CHANGE_MKDIR;
1221 }
1222 }
else if(local) {
1223 DavBool nochange =
FALSE;
1224 if(
SYNC_SYMLINK(dir) && nullstrcmp(link, local->link_target)) {
1225 ret =
REMOTE_CHANGE_LINK;
1226 nochange =
TRUE;
1227
1228 if(local->link_target) {
1229 LocalResource *local2 = local_resource_new(dir, db, local->path);
1230 if(type ==
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED && nullstrcmp(local->link_target, local2->link_target)) {
1231 ret =
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
1232 }
1233 local_resource_free(local2);
1234
1235 if(!nullstrcmp(link, local->link_target)) {
1236 ret =
REMOTE_NO_CHANGE;
1237 update_db =
TRUE;
1238 }
1239 }
1240 }
else if(issplit && local->hash && hash) {
1241 if(!strcmp(local->hash, hash)) {
1242
1243 nochange =
TRUE;
1244 }
1245 }
else if(local->etag) {
1246 cxstring e = cx_str(etag);
1247 if(cx_strprefix(e,
CX_STR(
"W/"))) {
1248 e = cx_strsubs(e,
2);
1249 }
1250 if(!strcmp(e.ptr, local->etag)) {
1251
1252 nochange =
TRUE;
1253 }
1254 }
1255
1256 if(!nochange) {
1257 if(!(exists && s.st_mtime != local->last_modified)) {
1258 type =
REMOTE_CHANGE_MODIFIED;
1259 }
1260 ret = type;
1261 }
1262 }
else if(link) {
1263
1264 ret =
REMOTE_CHANGE_LINK;
1265
1266 if(exists && type ==
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) {
1267
1268
1269 LocalResource *local2 = local_resource_new(dir, db, res->path);
1270 if(local2) {
1271 if(local2->link_target) {
1272 if(strcmp(link, local2->link_target)) {
1273 ret =
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
1274 }
1275 }
else {
1276 ret =
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED;
1277 }
1278
1279 local_resource_free(local2);
1280 }
1281 }
1282
1283 }
else if(exists) {
1284 ret = type;
1285 }
else {
1286 ret =
REMOTE_CHANGE_NEW;
1287 }
1288
1289
1290
1291 char *update_hash =
NULL;
1292 if (!iscollection &&
1293 !link &&
1294 (ret ==
REMOTE_CHANGE_MODIFIED || ret ==
REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) &&
1295 exists &&
1296 hash &&
1297 !dir->pull_skip_hashing)
1298 {
1299
1300
1301 char *local_hash = util_file_hash(local_path);
1302 if(local_hash) {
1303 if(!strcmp(hash, local_hash)) {
1304 ret =
REMOTE_NO_CHANGE;
1305 update_db =
TRUE;
1306 update_hash = local_hash;
1307
1308
1309
1310
1311
1312 if(local) {
1313 if(local->hash) {
1314 free(local->hash);
1315 }
1316 local->hash = local_hash;
1317 }
1318 }
else {
1319 free(local_hash);
1320 }
1321 }
1322 }
1323
1324
1325 while(ret ==
REMOTE_NO_CHANGE && local) {
1326
1327 if(dir->tagconfig) {
1328 DavXmlNode *tagsprop = dav_get_property_ns(res,
DAV_PROPS_NS,
"tags");
1329 CxList *remote_tags =
NULL;
1330 if(tagsprop) {
1331 remote_tags = parse_dav_xml_taglist(tagsprop);
1332 }
1333 char *remote_hash = create_tags_hash(remote_tags);
1334 if(nullstrcmp(remote_hash, local->remote_tags_hash)) {
1335 ret =
REMOTE_CHANGE_METADATA;
1336 }
1337 if(remote_hash) {
1338 free(remote_hash);
1339 }
1340 free_taglist(remote_tags);
1341
1342 if(ret ==
REMOTE_CHANGE_METADATA) {
1343 break;
1344 }
1345 }
1346
1347
1348 if((dir->metadata &
FINFO_XATTR) ==
FINFO_XATTR) {
1349 DavXmlNode *xattr = dav_get_property_ns(res,
DAV_PROPS_NS,
"xattributes");
1350 char *xattr_hash = get_xattr_hash(xattr);
1351 if(nullstrcmp(xattr_hash, local->xattr_hash)) {
1352 ret =
REMOTE_CHANGE_METADATA;
1353 break;
1354 }
1355 }
1356
1357
1358 DavXmlNode *finfo = dav_get_property_ns(res,
DAV_PROPS_NS,
"finfo");
1359 if((dir->metadata &
FINFO_MODE) ==
FINFO_MODE) {
1360 FileInfo f;
1361 finfo_get_values(finfo, &f);
1362 if(f.mode_set && f.mode != local->mode) {
1363 ret =
REMOTE_CHANGE_METADATA;
1364 break;
1365 }
1366 }
1367
1368 break;
1369 }
1370
1371
1372
1373 if(ret ==
REMOTE_NO_CHANGE && update_db) {
1374 if(!local) {
1375 local = calloc(
1,
sizeof(LocalResource));
1376 local->path = strdup(res->path);
1377
1378 cxMapPut(db->resources, cx_hash_key_str(local->path), local);
1379 }
1380
1381
1382 SYS_STAT s;
1383 if(!sys_stat(local_path, &s)) {
1384 sync_set_metadata_from_stat(local, &s);
1385 }
else {
1386 fprintf(stderr,
"stat failed for file: %s : %s", local_path, strerror(errno));
1387 }
1388 local_resource_set_etag(local, etag);
1389 if(!local->hash) {
1390 local->hash = update_hash;
1391 }
1392 if(link) {
1393 nullfree(local->link_target);
1394 local->link_target = link;
1395 }
1396 }
1397
1398 free(local_path);
1399 return ret;
1400 }
1401
1402 void sync_set_metadata_from_stat(LocalResource *local,
SYS_STAT *s) {
1403 local->last_modified = s->st_mtime;
1404 local->mode = s->st_mode &
07777;
1405 local->uid = s->st_uid;
1406 local->gid = s->st_gid;
1407 local->size = s->st_size;
1408 }
1409
1410 #ifdef _WIN32
1411 #define fseeko fseek
1412 #define ftello ftell
1413 #endif
1414
1415 static CxList* sync_download_changed_parts(
1416 DavResource *res,
1417 LocalResource *local,
1418 FILE *out,
1419 size_t blocksize,
1420 uint64_t *blockcount,
1421 int64_t *truncate_file,
1422 int *err)
1423 {
1424 CxList *updates = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
1425 updates->simple_destructor = (cx_destructor_func)filepart_free;
1426
1427 size_t local_numparts = local ? local->numparts :
0;
1428 fseeko(out,
0,
SEEK_END);
1429 off_t end = ftello(out);
1430
1431 int error =
0;
1432
1433 CxBuffer *buf = cxBufferCreate(
NULL, blocksize, cxDefaultAllocator,
0);
1434
1435 int64_t maxsize = -
1;
1436
1437 DavResource *part = res->children;
1438 uint64_t i =
0;
1439 while(part) {
1440 char *res_name = part->name;
1441 while(res_name[
0] ==
'0' && res_name[
1] !=
'\0') {
1442 res_name++;
1443 }
1444 uint64_t partnum =
0;
1445 if(util_strtouint(res_name, &partnum)) {
1446 DavBool download_part =
FALSE;
1447 char *etag = dav_get_string_property_ns(part,
"DAV:",
"getetag");
1448 if(partnum >= local_numparts) {
1449
1450 download_part =
TRUE;
1451 }
else {
1452 FilePart p = local->parts[partnum];
1453 if(etag) {
1454 if(nullstrcmp(etag, p.etag)) {
1455 download_part =
TRUE;
1456 }
1457 }
1458 }
1459
1460 uint64_t offset;
1461 int mul_err = util_uint_mul(partnum, blocksize, &offset);
1462 if(mul_err || offset >=
INT64_MAX) {
1463 error =
1;
1464 fprintf(stderr,
"Error: part number too high\n");
1465 break;
1466 }
1467
1468 int64_t block_end =
0;
1469 if(download_part) {
1470 if(fseeko(out, offset,
SEEK_SET)) {
1471 error =
1;
1472 fprintf(stderr,
"Error: fseek failed: %s\n", strerror(errno));
1473 break;
1474 }
1475 buf->pos =
0;
1476 buf->size =
0;
1477 if(dav_get_content(part, buf,(dav_write_func)cxBufferWrite)) {
1478 fprintf(stderr,
"Error: cannot download part: %s\n", part->name);
1479 error =
1;
1480 break;
1481 }
1482 if(fwrite(buf->space,
1, buf->size, out) ==
0) {
1483 perror(
"write");
1484 error =
1;
1485 break;
1486 }
1487
1488 FilePart *update = calloc(
1,
sizeof(FilePart));
1489 update->block = partnum;
1490 update->etag = etag ? strdup(etag) :
NULL;
1491 update->hash = dav_create_hash(buf->space, buf->size);
1492 cxListAdd(updates, update);
1493
1494 block_end = offset+buf->size;
1495 }
else {
1496 if(offset+blocksize > end) {
1497
1498
1499 block_end = end;
1500 }
else {
1501 block_end = offset+blocksize;
1502 }
1503 }
1504
1505 if(block_end > maxsize) {
1506 maxsize = block_end;
1507 }
1508
1509 i++;
1510 }
1511 part = part->next;
1512 }
1513
1514 cxBufferFree(buf);
1515
1516 if(error) {
1517 *err =
1;
1518 cxListDestroy(updates);
1519 return NULL;
1520 }
1521
1522 if(maxsize < end) {
1523 *truncate_file = maxsize;
1524 }
else {
1525 *truncate_file = -
1;
1526 }
1527
1528 *err =
0;
1529 *blockcount = i;
1530 return updates;
1531 }
1532
1533 int copy_file(
const char *from,
const char *to) {
1534 FILE *in = sys_fopen(from,
"rb");
1535 if(!in) {
1536 return 1;
1537 }
1538 FILE *out = sys_fopen(to,
"wb");
1539 if(!out) {
1540 fclose(in);
1541 return 1;
1542 }
1543
1544 cx_stream_copy(in, out, (cx_read_func)fread, (cx_write_func)fwrite);
1545 fclose(in);
1546 fclose(out);
1547
1548 return 0;
1549 }
1550
1551 typedef int (*renamefunc)(
const char*,
const char*);
1552
1553 int sync_move_resource(
1554 CmdArgs *a,
1555 SyncDirectory *dir,
1556 DavResource *res,
1557 LocalResource *content,
1558 DavBool copy,
1559 SyncDatabase *db,
1560 int *counter)
1561 {
1562 renamefunc fn = copy ? copy_file : sys_rename;
1563
1564 char *new_path = util_concat_path(dir->path, res->path);
1565 char *old_path = util_concat_path(dir->path, local_resource_path(content));
1566
1567 log_printf(
"%s: %s -> %s\n", copy?
"copy":
"move", content->path, res->path);
1568 if(fn(old_path, new_path)) {
1569 free(new_path);
1570 free(old_path);
1571 return 1;
1572 }
1573 (*counter)++;
1574
1575 char *etag = dav_get_string_property(res,
"D:getetag");
1576 char *content_hash = sync_get_content_hash(res);
1577
1578 LocalResource *local =
NULL;
1579 if(copy) {
1580
1581
1582 local = local_resource_copy(content, res->path);
1583 }
else {
1584
1585
1586 local = cxMapRemoveAndGet(db->resources, cx_hash_key_str(content->path));
1587 if(!local) {
1588
1589 local = content;
1590 }
1591 free(content->path);
1592 local->path = strdup(res->path);
1593 }
1594 cxMapPut(db->resources, cx_hash_key_str(local->path), local);
1595
1596 if(sync_store_metadata(dir, new_path, local, res)) {
1597 fprintf(stderr,
"Cannot store metadata: %s\n", res->path);
1598 }
1599
1600
1601
1602 if(local->hash) {
1603 free(local->hash);
1604 }
1605
1606 SYS_STAT s;
1607 if(sys_stat(new_path, &s)) {
1608 fprintf(stderr,
1609 "Cannot stat file %s: %s\n", new_path, strerror(errno));
1610 }
1611
1612
1613 local_resource_set_etag(local, etag);
1614 local->hash = nullstrdup(content_hash);
1615
1616 sync_set_metadata_from_stat(local, &s);
1617 local->skipped =
FALSE;
1618
1619 return 0;
1620 }
1621
1622 int sync_get_resource(
1623 CmdArgs *a,
1624 SyncDirectory *dir,
1625 const char *path,
1626 DavResource *res,
1627 SyncDatabase *db,
1628 DavBool update_db,
1629 int *counter)
1630 {
1631 char *link =
SYNC_SYMLINK(dir) ?
1632 dav_get_string_property_ns(res,
DAV_PROPS_NS,
"link") :
NULL;
1633
1634 LocalResource *local = cxMapGet(db->resources, cx_hash_key_str(path));
1635
1636 char *local_path;
1637 if(link) {
1638 char *res_path = resource_local_path(res);
1639 local_path = create_local_path(dir, res_path);
1640 free(res_path);
1641 }
else {
1642 local_path = create_local_path(dir, path);
1643 }
1644
1645 char *etag = dav_get_string_property(res,
"D:getetag");
1646 SYS_STAT s;
1647 memset(&s,
0,
sizeof(
SYS_STAT));
1648
1649 char *blocksize_str = dav_get_string_property_ns(res,
DAV_NS,
"split");
1650 uint64_t blocksize =
0;
1651 DavBool issplit =
FALSE;
1652 if(blocksize_str && !link) {
1653 if(!util_strtouint(blocksize_str, &blocksize)) {
1654 fprintf(stderr,
"Error: split property does not contain an integer.\n");
1655 return 1;
1656 }
1657 issplit =
TRUE;
1658 }
1659 CxList *part_updates =
NULL;
1660 uint64_t blockcount =
0;
1661 char *content_hash =
NULL;
1662
1663 if(res->iscollection && !issplit) {
1664
1665 return 0;
1666 }
1667
1668 int ret =
0;
1669
1670 char *tmp_path =
NULL;
1671 FILE *out =
NULL;
1672 if(!link) {
1673 if(!issplit) {
1674 tmp_path = create_tmp_download_path(local_path);
1675 if(!tmp_path) {
1676 fprintf(stderr,
"Cannot create tmp path for %s\n", local_path);
1677 free(local_path);
1678 return -
1;
1679 }
1680 out = sys_fopen(tmp_path ,
"wb");
1681 }
else {
1682 out = sys_fopen(local_path,
"r+b");
1683 if(!out && errno ==
ENOENT) {
1684 out = sys_fopen(local_path,
"wb");
1685 }
1686 }
1687 if(!out) {
1688 fprintf(stderr,
"Cannot open output file: %s\n", local_path);
1689 free(local_path);
1690 if(tmp_path) {
1691 free(tmp_path);
1692 }
1693 return -
1;
1694 }
1695 }
1696
1697 int64_t truncate_file = -
1;
1698 if(!link) {
1699 log_printf(
"get: %s\n", path);
1700 if(issplit) {
1701 part_updates = sync_download_changed_parts(res, local, out, blocksize, &blockcount, &truncate_file, &ret);
1702 }
else {
1703 ret = dav_get_content(res, out, (dav_write_func)fwrite);
1704 }
1705 fclose(out);
1706 }
else {
1707 log_printf(
"link: %s -> %s\n", path, link);
1708 if(sys_symlink(link, local_path)) {
1709 perror(
"symlink");
1710 ret =
1;
1711 }
1712 }
1713
1714 if(issplit || (
SYNC_HASHING(dir) && !link)) {
1715 if(truncate_file >=
0) {
1716
1717 if(sys_truncate(local_path, truncate_file)) {
1718 perror(
"truncate");
1719 }
1720 }
1721
1722 char *res_hash = sync_get_content_hash(res);
1723 if(res_hash) {
1724 content_hash = res_hash;
1725 }
else {
1726 content_hash = util_file_hash(local_path);
1727 }
1728 }
1729
1730 if(ret ==
0) {
1731 (*counter)++;
1732
1733 if(tmp_path) {
1734 if(dir->trash && dir->backuppull) {
1735 move_to_trash(dir, local_path);
1736 }
1737 if(sys_rename(tmp_path, local_path)) {
1738 fprintf(
1739 stderr,
1740 "Cannot rename file %s to %s\n",
1741 tmp_path,
1742 local_path);
1743 perror(
"");
1744 free(tmp_path);
1745 free(local_path);
1746 return -
1;
1747 }
1748 }
1749
1750 }
else if(tmp_path) {
1751 if(sys_unlink(tmp_path)) {
1752 fprintf(stderr,
"Cannot remove tmp file: %s\n", tmp_path);
1753 }
1754 }
1755
1756 if(update_db && ret ==
0) {
1757 if(!local) {
1758
1759 local = calloc(
1,
sizeof(LocalResource));
1760 local->path = strdup(path);
1761 cxMapPut(db->resources, cx_hash_key_str(local->path), local);
1762 }
1763
1764 if(sync_store_metadata(dir, local_path, local, res)) {
1765 fprintf(stderr,
"Cannot store metadata: %s\n", path);
1766 }
1767
1768 if(local->etag) {
1769 free(local->etag);
1770 local->etag =
NULL;
1771 }
1772 if(local->hash) {
1773 free(local->hash);
1774 local->hash =
NULL;
1775 }
1776 if(local->link_target) {
1777 free(local->link_target);
1778 local->link_target =
NULL;
1779 }
1780
1781 stat_func statfn;
1782 if(link) {
1783 local->link_target = strdup(link);
1784 statfn = sys_lstat;
1785 }
else {
1786 statfn = sys_stat;
1787 }
1788
1789 update_parts(local, part_updates, blockcount);
1790
1791 if(statfn(local_path, &s)) {
1792 fprintf(stderr,
1793 "Cannot stat file %s: %s\n", local_path, strerror(errno));
1794 }
1795
1796
1797 if(!issplit) {
1798 local_resource_set_etag(local, etag);
1799 }
1800 if(content_hash) {
1801 local->hash = content_hash;
1802 }
1803 sync_set_metadata_from_stat(local, &s);
1804 local->skipped =
FALSE;
1805 }
1806
1807 if(tmp_path) {
1808 free(tmp_path);
1809 }
1810 free(local_path);
1811 return ret;
1812 }
1813
1814 int sync_get_collection(
1815 CmdArgs *a,
1816 SyncDirectory *dir,
1817 DavResource *res,
1818 SyncDatabase *db)
1819 {
1820 cxstring res_path = cx_str(res->path);
1821 if(res_path.length >
0 && res_path.ptr[res_path.length-
1] ==
'/') {
1822 res_path.length--;
1823 }
1824
1825
1826
1827 char *local_path = create_local_path(dir, res->path);
1828
1829
1830 log_printf(
"get: %s\n", res->path);
1831
1832
1833 if(sys_mkdir(local_path) && errno !=
EEXIST) {
1834 fprintf(stderr,
1835 "Cannot create directory %s: %s",
1836 local_path, strerror(errno));
1837 free(local_path);
1838 return 1;
1839 }
1840
1841
1842 SYS_STAT s;
1843 if(sys_stat(local_path, &s)) {
1844 fprintf(stderr,
"Cannot stat directory %s: %s", local_path, strerror(errno));
1845 free(local_path);
1846 return 1;
1847 }
1848
1849
1850 LocalResource *local = cxMapGet(db->resources, cx_hash_key(res_path.ptr, res_path.length));
1851 if(!local) {
1852 local = calloc(
1,
sizeof(LocalResource));
1853 local->path = cx_strdup(res_path).ptr;
1854 cxMapPut(db->resources, cx_hash_key_str(local->path), local);
1855 }
1856 local->isdirectory =
1;
1857
1858
1859 if(local->etag) {
1860 free(local->etag);
1861 local->etag =
NULL;
1862 }
1863 if(local->hash) {
1864 free(local->hash);
1865 local->hash =
NULL;
1866 }
1867 if(local->link_target) {
1868 free(local->link_target);
1869 local->link_target =
NULL;
1870 }
1871 local->skipped =
0;
1872 local->size =
0;
1873
1874
1875 sync_set_metadata_from_stat(local, &s);
1876 if(sync_store_metadata(dir, local_path, local, res)) {
1877 fprintf(stderr,
"Cannot store metadata: %s\n", res->path);
1878 }
1879
1880
1881 free(local_path);
1882 return 0;
1883 }
1884
1885 int sync_remove_local_resource(SyncDirectory *dir, LocalResource *res) {
1886 char *local_path = create_local_path(dir, res->path);
1887 SYS_STAT s;
1888 if(sys_stat(local_path, &s)) {
1889 free(local_path);
1890 return -
2;
1891 }
1892
1893 if(
S_ISDIR(s.st_mode)) {
1894 free(local_path);
1895 return -
1;
1896 }
1897
1898 if(s.st_mtime != res->last_modified) {
1899 free(local_path);
1900 return -
2;
1901 }
1902
1903 log_printf(
"delete: %s\n", res->path);
1904 int ret =
0;
1905 if(dir->trash) {
1906 move_to_trash(dir, local_path);
1907 }
else if(sys_unlink(local_path)) {
1908 fprintf(stderr,
"Cannot remove file %s\n", local_path);
1909 ret = -
2;
1910 }
1911 free(local_path);
1912
1913 return ret;
1914 }
1915
1916 int sync_remove_local_directory(SyncDirectory *dir, LocalResource *res) {
1917 int ret =
0;
1918 char *local_path = create_local_path(dir, res->path);
1919
1920 log_printf(
"delete: %s\n", res->path);
1921 if(rmdir(local_path)) {
1922
1923
1924
1925
1926 if(errno !=
ENOTEMPTY) {
1927 fprintf(stderr,
"rmdir: %s : %s\n", local_path, strerror(errno));
1928 }
1929 ret =
1;
1930 }
1931
1932 free(local_path);
1933 return ret;
1934 }
1935
1936 void rename_conflict_file(SyncDirectory *dir, SyncDatabase *db,
char *path, DavBool copy) {
1937 char *local_path = create_local_path(dir, path);
1938 char *parent = util_parent_path(local_path);
1939
1940 renamefunc fn = copy ? copy_file : sys_rename;
1941
1942 int rev =
0;
1943 SYS_STAT s;
1944 int loop =
1;
1945 do {
1946 char *res_parent = util_parent_path(path);
1947 const char *res_name = util_resource_name(path);
1948
1949 cxmutstr new_path = cx_asprintf(
1950 "%sorig.%d.%s",
1951 parent,
1952 rev,
1953 res_name);
1954 cxmutstr new_res_path = cx_asprintf(
1955 "%sorig.%d.%s",
1956 res_parent,
1957 rev,
1958 res_name);
1959
1960
1961 if(sys_stat(new_path.ptr, &s)) {
1962 if(errno ==
ENOENT) {
1963 loop =
0;
1964 log_printf(
"conflict: %s\n", local_path);
1965 if(fn(local_path, new_path.ptr)) {
1966
1967 fprintf(
1968 stderr,
1969 "Cannot rename file %s to %s\n",
1970 local_path,
1971 new_path.ptr);
1972 }
else {
1973 LocalResource *conflict = calloc(
1,
sizeof(LocalResource));
1974 conflict->path = strdup(new_res_path.ptr);
1975 conflict->conflict_source = strdup(path);
1976 cxMapPut(db->conflict, cx_hash_key_str(new_res_path.ptr), conflict);
1977 }
1978 }
1979 }
1980 rev++;
1981 free(res_parent);
1982 free(new_path.ptr);
1983 free(new_res_path.ptr);
1984
1985 }
while(loop);
1986 free(parent);
1987 free(local_path);
1988 }
1989
1990 char* create_tmp_download_path(
char *path) {
1991 char *new_path =
NULL;
1992 char *parent = util_parent_path(path);
1993 for (
int i=
0;;i++) {
1994 cxmutstr np = cx_asprintf(
1995 "%sdownload%d-%s",
1996 parent,
1997 i,
1998 util_resource_name(path));
1999
2000 SYS_STAT s;
2001 if(sys_stat(np.ptr, &s)) {
2002 if(errno ==
ENOENT) {
2003 new_path = np.ptr;
2004 }
2005 break;
2006 }
2007 free(np.ptr);
2008 };
2009
2010 free(parent);
2011 return new_path;
2012 }
2013
2014 void move_to_trash(SyncDirectory *dir,
char *path) {
2015 char *new_path =
NULL;
2016 for (
int i=
0;;i++) {
2017 cxmutstr np = cx_asprintf(
2018 "%s%d-%s",
2019 dir->trash,
2020 i,
2021 util_resource_name(path));
2022
2023 SYS_STAT s;
2024 if(sys_stat(np.ptr, &s)) {
2025 if(errno ==
ENOENT) {
2026 new_path = np.ptr;
2027 }
2028 break;
2029 }
2030 free(np.ptr);
2031 };
2032
2033 if(!new_path) {
2034 fprintf(stderr,
"Cannot move file %s to trash.\n", path);
2035 return;
2036 }
2037
2038 if(sys_rename(path, new_path)) {
2039
2040 fprintf(
2041 stderr,
2042 "Cannot rename file %s to %s\n",
2043 path,
2044 new_path);
2045 }
2046
2047 free(new_path);
2048 }
2049
2050 static int res_isconflict(SyncDatabase *db, LocalResource *res) {
2051 return cxMapGet(db->conflict, cx_hash_key_str(res->path)) ?
1 :
0;
2052 }
2053
2054 int cmd_push(CmdArgs *a, DavBool outgoing, DavBool archive) {
2055 if(a->argc !=
1) {
2056 fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
2057 return -
1;
2058 }
2059
2060
2061 SyncTagFilter* tagfilter = parse_tagfilter_string(
2062 cmd_getoption(a,
"tags"),
DAV_SYNC_TAGFILTER_SCOPE_RESOURCE);
2063 if (!tagfilter) {
2064 fprintf(stderr,
"Malformed tag filter\n");
2065 return -
1;
2066 }
2067
2068 SyncDirectory *dir = scfg_get_dir(a->argv[
0]);
2069 if(!dir) {
2070 fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
2071 return -
1;
2072 }
2073 if(scfg_check_dir(dir)) {
2074 return -
1;
2075 }
2076 if(logfile_open(dir)) {
2077 return -
1;
2078 }
2079 if(cmd_getoption(a,
"snapshot")) {
2080 if(dir->versioning) {
2081 dir->versioning->always =
TRUE;
2082 }
else {
2083 fprintf(stderr,
"Error: versioning not configured for the sync directory\nAbort.\n");
2084 return -
1;
2085 }
2086 }
2087
2088 int cmd = archive ?
SYNC_CMD_ARCHIVE :
SYNC_CMD_PUSH;
2089 if((dir->allow_cmd & cmd) != cmd) {
2090 fprintf(stderr,
"Command ''%s'' is not allowed for this sync dir\n", archive ?
"archive" :
"push");
2091 print_allowed_cmds(dir);
2092 return -
1;
2093 }
2094
2095 DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
2096 if(!repo) {
2097 fprintf(stderr,
"Unkown repository %s\n", dir->name);
2098 return -
1;
2099 }
2100
2101 SyncDatabase *db = load_db(dir->database);
2102 if(!db) {
2103 fprintf(stderr,
"Cannot load database file: %s\n", dir->database);
2104 return -
1;
2105 }
2106 remove_deleted_conflicts(dir, db);
2107
2108 DavSession *sn = create_session(a, ctx, repo, dir->collection);
2109 cxMempoolRegister(sn->mp, db, (cx_destructor_func)destroy_db);
2110 if (cmd_getoption(a,
"verbose")) {
2111 curl_easy_setopt(sn->handle,
CURLOPT_VERBOSE,
1L);
2112 curl_easy_setopt(sn->handle,
CURLOPT_STDERR, stderr);
2113 }
2114 if(
SYNC_STORE_HASH(dir)) {
2115 sn->flags |=
DAV_SESSION_STORE_HASH;
2116 }
2117
2118 DavBool restore_removed = cmd_getoption(a,
"restore-removed") ?
1 :
0;
2119 DavBool restore_modified = cmd_getoption(a,
"restore-modified") ?
1 :
0;
2120 DavBool restore = restore_removed || restore_modified;
2121 int depth = restore ? -
1 :
0;
2122
2123 DavResource *root = dav_query(sn,
"select D:getetag,idav:status from / with depth = %d", depth);
2124 if(!root) {
2125 log_resource_error(sn,
"/");
2126 dav_session_destroy(sn);
2127 log_error(
"Abort\n");
2128 return -
1;
2129 }
2130
2131 CxMap *svrres =
NULL;
2132 if(restore) {
2133 svrres = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS,
1024);
2134 res2map(root, svrres);
2135 }
2136
2137 int cdt = cmd_getoption(a,
"conflict") ?
0 :
1;
2138
2139
2140 DavBool locked =
FALSE;
2141 char *locktokenfile =
NULL;
2142 if((dir->lockpush || cmd_getoption(a,
"lock")) && !cmd_getoption(a,
"nolock") && !outgoing) {
2143 if(
dav_lock_t(root, dir->lock_timeout)) {
2144 log_resource_error(sn,
"/");
2145 dav_session_destroy(sn);
2146 log_error(
"Abort\n");
2147 return -
1;
2148 }
2149 DavLock *lock = dav_get_lock(sn,
"/");
2150 if(lock) {
2151 log_printf(
"Lock-Token: %s\n", lock->token);
2152 }
2153 locked =
TRUE;
2154 locktokenfile = create_locktoken_file(dir->name, lock->token);
2155 }
2156
2157 DavBool remove_file = cmd_getoption(a,
"remove") ?
1 :
0;
2158
2159 CxMap *db_hashes =
NULL;
2160 if(
SYNC_HASHING(dir)) {
2161 db_hashes = create_hash_index(db);
2162 }
2163
2164 int sync_success =
0;
2165 int sync_delete =
0;
2166 int sync_conflict =
0;
2167 int sync_error =
0;
2168
2169 CxList *ls_new = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
2170 CxList *ls_modified = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
2171 CxList *ls_conflict = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
2172 CxList *ls_update = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
2173 CxList *ls_delete = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
2174 CxList *ls_move = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
2175 CxList *ls_copy = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
2176 CxList *ls_mkcol = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)resource_path_cmp,
CX_STORE_POINTERS);
2177
2178
2179
2180
2181
2182 CxList *resources = local_scan(dir, db);
2183 CxMap *resources_map = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, resources->size+
16);
2184
2185 CxIterator iter = cxListIterator(resources);
2186 cx_foreach(LocalResource *, local_res, iter) {
2187
2188
2189
2190 if(res_matches_dir_filter(dir, local_res->path+
1)) {
2191 continue;
2192 }
2193
2194 if(dir->filter.tags) {
2195 CxIterator tag_iter = cxListIterator(dir->filter.tags);
2196 cx_foreach(SyncTagFilter *, tf, tag_iter) {
2197 if(!localres_matches_tags(dir, local_res, tf)) {
2198 continue;
2199 }
2200 }
2201 }
2202
2203
2204
2205 cxMapPut(resources_map, cx_hash_key_str(local_res->path), local_res);
2206
2207
2208 if(!localres_matches_tags(dir, local_res, tagfilter)) {
2209 if(!remove_file) {
2210 LocalResource *dbres = cxMapGet(
2211 db->resources,
2212 cx_hash_key_str(local_res->path));
2213 if(dbres) {
2214
2215 dbres->keep =
TRUE;
2216 }
2217 }
2218 continue;
2219 }
2220
2221
2222 if(res_isconflict(db, local_res)) {
2223 cxListAdd(ls_conflict, local_res);
2224 continue;
2225 }
2226
2227 int is_changed = local_resource_is_changed(
2228 dir,
2229 db,
2230 local_res,
2231 svrres,
2232 restore_removed,
2233 restore_modified);
2234 if(is_changed) {
2235 if(local_res->isdirectory) {
2236 cxListAdd(ls_mkcol, local_res);
2237 }
else if(local_res->isnew) {
2238 cxListAdd(ls_new, local_res);
2239 }
else {
2240 cxListAdd(ls_modified, local_res);
2241 }
2242 }
else if(local_res->metadata_updated) {
2243 cxListAdd(ls_update, local_res);
2244 }
2245
2246 if(local_res->isnew) {
2247 if(local_resource_load_metadata(dir, local_res)) {
2248 log_error(
2249 "Failed to load metadata: %s\n",
2250 local_resource_path(local_res));
2251 }
2252 }
2253 }
2254
2255 if(
SYNC_STORE_HASH(dir)) {
2256
2257
2258 CxMutIterator mut_iter = cxListMutIterator(ls_new);
2259 cx_foreach(LocalResource *, local, mut_iter) {
2260 if(local->isdirectory || local->link_target) {
2261 continue;
2262 }
2263
2264 char *local_path = util_concat_path(dir->path, local_resource_path(local));
2265 char *hash = util_file_hash(local_path);
2266 local->hash = hash;
2267
2268 LocalResource *origin = cxMapGet(db_hashes, cx_hash_key_str(hash));
2269 if(origin) {
2270 local->origin = local_resource_copy(origin, origin->path);
2271
2272
2273
2274 if(cxMapGet(resources_map, cx_hash_key_str(origin->path))) {
2275 cxListAdd(ls_copy, local);
2276 }
else {
2277 cxListAdd(ls_move, local);
2278
2279 cxMapPut(resources_map, cx_hash_key_str(origin->path), local);
2280 }
2281
2282 cxIteratorFlagRemoval(mut_iter);
2283 }
2284
2285 free(local_path);
2286 }
2287 }
2288
2289
2290 iter = cxMapIterator(db->resources);
2291 LocalResource *local;
2292 CxList *removed_res = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
2293 cx_foreach(CxMapEntry *, entry, iter) {
2294 LocalResource *local = entry->value;
2295
2296 if(res_matches_dir_filter(dir, local->path+
1)) {
2297 cxMapRemove(db->resources, local->path);
2298 continue;
2299 }
2300 CxIterator tag_iter = cxListIterator(dir->filter.tags);
2301 cx_foreach(SyncTagFilter *, tf, tag_iter) {
2302 if(!localres_matches_tags(dir, local, tf)) {
2303 cxMapRemove(db->resources, local->path);
2304 continue;
2305 }
2306 }
2307
2308 if(!cxMapGet(resources_map, *entry->key)) {
2309
2310
2311
2312 if(!archive) {
2313 cxListAdd(ls_delete, local);
2314 }
else {
2315 cxListInsert(removed_res,
0, local);
2316 }
2317 }
2318 }
2319 iter = cxListIterator(removed_res);
2320 cx_foreach(LocalResource *, local, iter) {
2321 cxMapRemove(db->resources, local->path);
2322 }
2323
2324 cxListSort(ls_new);
2325 cxListSort(ls_modified);
2326 cxListSort(ls_conflict);
2327 cxListSort(ls_update);
2328 cxListSort(ls_delete);
2329 cxListSort(ls_move);
2330 cxListSort(ls_copy);
2331 cxListSort(ls_mkcol);
2332
2333 if(outgoing) {
2334 print_outgoing(
2335 a,
2336 ls_new,
2337 ls_modified,
2338 ls_conflict,
2339 ls_update,
2340 ls_delete,
2341 ls_move,
2342 ls_copy,
2343 ls_mkcol);
2344 return 0;
2345 }
2346
2347
2348
2349
2350
2351 int ret =
0;
2352 int error =
0;
2353
2354
2355 iter = cxListIterator(ls_mkcol);
2356 cx_foreach(LocalResource *, local_res, iter) {
2357 if(sync_shutdown) {
2358 break;
2359 }
2360
2361 DavResource *res = dav_resource_new(sn, local_res->path);
2362 if(!res) {
2363 log_resource_error(sn, local_res->path);
2364 ret = -
1;
2365 sync_error++;
2366 }
2367
2368 int abort =
0;
2369
2370 dav_exists(res);
2371 if(sn->error ==
DAV_NOT_FOUND) {
2372
2373
2374 log_printf(
"mkcol: %s\n", local_res->path);
2375 if(sync_mkdir(dir, res, local_res) && sn->error !=
DAV_METHOD_NOT_ALLOWED) {
2376 log_resource_error(sn, res->path);
2377 ret = -
1;
2378 sync_error++;
2379 error =
1;
2380 abort =
1;
2381 }
2382 }
else if(sn->error !=
DAV_OK) {
2383
2384 log_resource_error(sn, local_res->path);
2385 ret = -
1;
2386 sync_error++;
2387 error =
1;
2388 abort =
1;
2389 }
2390
2391 if(!abort) {
2392
2393 if(local_res->metadata_updated) {
2394 sync_update_metadata(dir, sn, res, local_res);
2395 }
2396
2397
2398
2399
2400 LocalResource *dbres = cxMapGet(db->resources, cx_hash_key_str(local_res->path));
2401 cxMapPut(db->resources, cx_hash_key_str(local_res->path), local_res);
2402 }
2403
2404 dav_resource_free(res);
2405 }
2406
2407
2408 DavBool copy =
TRUE;
2409 if(!ls_copy) {
2410 copy =
FALSE;
2411 ls_copy = ls_move;
2412 }
2413 iter = cxListIterator(ls_copy);
2414 for(
int i=
0;i<
2;i++) {
2415 cx_foreach(LocalResource*, local, iter) {
2416 if(sync_shutdown) {
2417 break;
2418 }
2419
2420 int err =
0;
2421 DavResource *res = dav_resource_new(sn, local->path);
2422 if(dav_exists(res)) {
2423 log_printf(
"conflict: %s\n", local->path);
2424 local->last_modified =
0;
2425 nullfree(local->etag);
2426 local->etag =
NULL;
2427 nullfree(local->hash);
2428 local->hash =
NULL;
2429 local->skipped =
TRUE;
2430 sync_conflict++;
2431 }
else {
2432 DavResource *origin_res = dav_resource_new(sn, local->origin->path);
2433 int origin_changed = remote_resource_is_changed(
2434 sn,
2435 dir,
2436 db,
2437 origin_res,
2438 local->origin,
2439 NULL);
2440 if(origin_changed) {
2441
2442 cxListInsert(ls_modified,
0, local);
2443 }
else {
2444 log_printf(
"%s: %s -> %s\n", copy ?
"copy":
"move", local->origin->path, local->path);
2445 err = sync_move_remote_resource(
2446 dir,
2447 db,
2448 origin_res,
2449 local,
2450 copy,
2451 &sync_success);
2452 }
2453 }
2454
2455 if(err) {
2456 sync_error++;
2457 log_resource_error(sn, res->path);
2458 ret = -
1;
2459 error =
1;
2460 }
else {
2461 LocalResource *dbres = cxMapGet(db->resources, cx_hash_key_str(local->path));
2462 cxMapPut(db->resources, cx_hash_key_str(local->path), local);
2463 }
2464 }
2465 copy =
FALSE;
2466 iter = cxListIterator(ls_move);
2467 }
2468
2469
2470
2471 iter = cxListIterator(ls_new);
2472 for(
int i=
0;i<
2;i++) {
2473 cx_foreach(LocalResource*, local_res, iter) {
2474 if(sync_shutdown) {
2475 break;
2476 }
2477
2478 int err =
0;
2479
2480 DavResource *res = dav_resource_new(sn, local_res->path);
2481 if(!res) {
2482 log_resource_error(sn, local_res->path);
2483 ret = -
1;
2484 sync_error++;
2485 }
else {
2486 DavBool equal =
FALSE;
2487 DavBool res_conflict =
FALSE;
2488 int changed = remote_resource_is_changed(sn, dir, db, res, local_res, &equal);
2489 if(equal) {
2490 char *etag = dav_get_string_property(res,
"D:getetag");
2491 if(local_res->metadata_updated) {
2492 cxListInsert(ls_update,
0, local_res);
2493 }
else if(etag) {
2494
2495 if(local_res->etag) {
2496 free(local_res->etag);
2497 }
2498 local_res->etag = strdup(etag);
2499 }
2500 }
else if(cdt && changed) {
2501 log_printf(
"conflict: %s\n", local_res->path);
2502 local_res->last_modified =
0;
2503 nullfree(local_res->etag);
2504 local_res->etag =
NULL;
2505 nullfree(local_res->hash);
2506 local_res->hash =
NULL;
2507 local_res->skipped =
TRUE;
2508 sync_conflict++;
2509
2510 if(local_res->link_target) {
2511 free(local_res->link_target);
2512 local_res->link_target = local_res->link_target_db;
2513 local_res->link_target_db =
NULL;
2514 }
2515
2516 res_conflict =
TRUE;
2517 }
else {
2518 if(local_res->link_target) {
2519 log_printf(
2520 "link: %s -> %s\n",
2521 local_res->path,
2522 local_res->link_target);
2523 }
else {
2524 log_printf(
"put: %s\n", local_res->path);
2525 }
2526 if(sync_put_resource(dir, res, local_res, &sync_success)) {
2527 sync_error++;
2528 log_resource_error(sn, res->path);
2529 ret = -
1;
2530 error =
1;
2531
2532 err =
1;
2533 }
2534 }
2535
2536 if(!err) {
2537 LocalResource *dbres = cxMapRemoveAndGet(db->resources, cx_hash_key_str(local_res->path));
2538
2539
2540 if(!res_conflict || dbres) {
2541 cxMapPut(db->resources, cx_hash_key_str(local_res->path), local_res);
2542 }
2543 }
2544 }
2545
2546 dav_resource_free(res);
2547 }
2548 iter = cxListIterator(ls_modified);
2549 }
2550
2551
2552 iter = cxListIterator(ls_update);
2553 cx_foreach(LocalResource *, local_res, iter) {
2554 if(sync_shutdown) {
2555 break;
2556 }
2557
2558 DavResource *res = dav_resource_new(sn, local_res->path);
2559 if(local_res->metadata_updated) {
2560 log_printf(
"update: %s\n", local_res->path);
2561 if(!sync_update_metadata(dir, sn, res, local_res)) {
2562 LocalResource *dbres = cxMapGet(db->resources, cx_hash_key_str(local_res->path));
2563 cxMapPut(db->resources, cx_hash_key_str(local_res->path), local_res);
2564 }
2565 }
2566 }
2567
2568
2569 cxListSort(ls_delete);
2570
2571 CxList *cols = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)localres_cmp_path_desc,
CX_STORE_POINTERS);
2572 CxList *cols_del = cols;
2573 CxList *col_list = cols;
2574 CxList *deletelist = ls_delete;
2575 for(
int i=
0;i<
2;i++) {
2576
2577
2578
2579 iter = cxListIterator(deletelist);
2580 cx_foreach(LocalResource *, local, iter) {
2581 if(sync_shutdown) {
2582 break;
2583 }
2584 if(local->keep) {
2585 continue;
2586 }
2587 if(sync_delete_remote_resource(dir, sn, local, &sync_delete, col_list)) {
2588 if(sn->error !=
DAV_NOT_FOUND) {
2589 log_resource_error(sn, local->path);
2590 sync_error++;
2591 break;
2592 }
2593 }
else {
2594 LocalResource *dbres = cxMapRemoveAndGet(db->resources, cx_hash_key_str(local->path));
2595
2596 }
2597 }
2598 cxListSort(cols);
2599 deletelist = cols;
2600 col_list =
NULL;
2601 }
2602
2603 cxListDestroy(cols_del);
2604
2605
2606 if(locked) {
2607 if(dav_unlock(root)) {
2608 log_resource_error(sn,
"/");
2609 ret = -
1;
2610 }
else {
2611 locked =
FALSE;
2612 }
2613 }
2614
2615
2616 if(store_db(db, dir->database, dir->db_settings)) {
2617 log_error(
"Cannot store sync db\n");
2618 ret = -
2;
2619 }
2620
2621
2622 if(!locked && locktokenfile) {
2623 remove(locktokenfile);
2624 }
2625
2626 dav_session_destroy(sn);
2627
2628
2629 if(ret != -
2) {
2630 char *str_success = sync_success ==
1 ?
"file" :
"files";
2631 char *str_delete = sync_delete ==
1 ?
"file" :
"files";
2632 char *str_conflict = sync_conflict ==
1 ?
"conflict" :
"conflicts";
2633 char *str_error = sync_error ==
1 ?
"error" :
"errors";
2634
2635 log_printf(
"Result: %d %s pushed, ", sync_success, str_success);
2636 if(!archive) {
2637 log_printf(
"%d %s deleted, ", sync_delete, str_delete);
2638 }
2639 log_printf(
"%d %s, %d %s\n",
2640 sync_conflict, str_conflict, sync_error, str_error);
2641 }
2642
2643 return ret;
2644 }
2645
2646 int cmd_restore(CmdArgs *a) {
2647 char *syncdir = cmd_getoption(a,
"syncdir");
2648
2649 if(!syncdir && a->argc ==
0) {
2650 fprintf(stderr,
"No syncdir or files specified\n");
2651 return -
1;
2652 }
2653
2654 char *version = cmd_getoption(a,
"version");
2655 if(version) {
2656 if(a->argc !=
1) {
2657 fprintf(stderr,
"If the -V option is enabled, only one file can be specified\n");
2658 return -
1;
2659 }
2660 }
2661
2662 SyncDirectory *dir =
NULL;
2663 CxMap *files =
NULL;
2664 if(syncdir) {
2665 dir = scfg_get_dir(syncdir);
2666 }
2667 if(logfile_open(dir)) {
2668 return -
1;
2669 }
2670
2671 LocalResource nres;
2672 if(a->argc >
0) {
2673 files = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, a->argc+
8);
2674
2675 SyncDirectory *sd =
NULL;
2676 for(
int i=
0;i<a->argc;i++) {
2677 SyncFile f;
2678 int err = sync_get_file(a, a->argv[i], &f,
FALSE);
2679 if(err) {
2680 sync_print_get_file_err(a->argv[i], err);
2681 return 1;
2682 }
2683 if(!sd) {
2684 sd = f.dir;
2685 }
else {
2686 if(f.dir != sd) {
2687 fprintf(stderr,
"Not all files are in the same syncdir\n");
2688 return 1;
2689 }
2690 }
2691
2692 cxMapPut(files, cx_hash_key_str(f.path), &nres);
2693 }
2694 dir = sd;
2695 }
2696
2697 if(!dir) {
2698 fprintf(stderr,
"Unknown sync dir: %s\n", syncdir);
2699 return -
1;
2700 }
2701 if(scfg_check_dir(dir)) {
2702 return -
1;
2703 }
2704
2705 if((dir->allow_cmd &
SYNC_CMD_RESTORE) !=
SYNC_CMD_RESTORE) {
2706 fprintf(stderr,
"Command ''''restore'''' is not allowed for this sync dir\n");
2707 print_allowed_cmds(dir);
2708 return -
1;
2709 }
2710
2711 DavBool restore_modified = cmd_getoption(a,
"restore-modified") ?
1 :
0;
2712 DavBool restore_removed = cmd_getoption(a,
"restore-removed") ?
1 :
0;
2713 if(!restore_modified && !restore_removed) {
2714 restore_modified =
1;
2715 restore_removed =
1;
2716 }
2717
2718 SyncDatabase *db = load_db(dir->database);
2719 if(!db) {
2720 log_error(
"Cannot load database file: %s\n", dir->database);
2721 return -
1;
2722 }
2723 remove_deleted_conflicts(dir, db);
2724
2725 CxList *modified = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)localres_cmp_path,
CX_STORE_POINTERS);
2726 CxList *deleted = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)localres_cmp_path,
CX_STORE_POINTERS);
2727
2728
2729
2730 CxIterator i = cxMapIterator(files ? files : db->resources);
2731 cx_foreach(CxMapEntry *, entry, i) {
2732 LocalResource *resource = entry->value;
2733 if(resource == &nres) {
2734 resource = cxMapGet(db->resources, *entry->key);
2735 if(!resource) {
2736 continue;
2737 }
2738 }
2739
2740 char *file_path = create_local_path(dir, resource->path);
2741 SYS_STAT s;
2742 if(sys_stat(file_path, &s)) {
2743 if(errno ==
ENOENT) {
2744 if(restore_removed) {
2745 cxListAdd(deleted, resource);
2746 }
2747 }
else {
2748 log_error(
"Cannot stat file: %s\n", file_path);
2749 log_error(
"%s\n", strerror(errno));
2750 }
2751 }
else {
2752 if(files) {
2753 cxListAdd(modified, resource);
2754 }
else if(!resource->isdirectory && !
S_ISDIR(s.st_mode)) {
2755 if(resource->last_modified != s.st_mtime || resource->size != s.st_size) {
2756 if(restore_modified) {
2757 cxListAdd(modified, resource);
2758 }
2759 }
2760 }
2761 }
2762
2763 free(file_path);
2764 }
2765
2766 if(files) {
2767 cxMapDestroy(files);
2768 }
2769
2770 int ret =
0;
2771
2772
2773 DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
2774 if(!repo) {
2775 log_error(
"Unkown repository %s\n", dir->name);
2776 return -
1;
2777 }
2778 DavSession *sn = create_session(a, ctx, repo, dir->collection);
2779 cxMempoolRegister(sn->mp, db, (cx_destructor_func)destroy_db);
2780 if (cmd_getoption(a,
"verbose")) {
2781 curl_easy_setopt(sn->handle,
CURLOPT_VERBOSE,
1L);
2782 curl_easy_setopt(sn->handle,
CURLOPT_STDERR, stderr);
2783 }
2784
2785
2786 char *locktokenfile =
NULL;
2787 DavBool locked =
FALSE;
2788 DavResource *root = dav_resource_new(sn,
"/");
2789 root->iscollection =
TRUE;
2790 if((dir->lockpush || cmd_getoption(a,
"lock")) && !cmd_getoption(a,
"nolock")) {
2791 if(
dav_lock_t(root, dir->lock_timeout)) {
2792 log_resource_error(sn,
"/");
2793 dav_session_destroy(sn);
2794 log_error(
"Abort\n");
2795 return -
1;
2796 }
2797 DavLock *lock = dav_get_lock(sn,
"/");
2798 if(lock) {
2799 log_printf(
"Lock-Token: %s\n", lock->token);
2800 }
2801 locked =
TRUE;
2802 locktokenfile = create_locktoken_file(dir->name, lock->token);
2803 }
2804
2805 int sync_success =
0;
2806 int sync_error =
0;
2807
2808
2809
2810
2811 cxListSort(deleted);
2812
2813 CxIterator iter = cxListIterator(modified);
2814 for(
int i=
0;i<
2;i++) {
2815 cx_foreach(LocalResource *, resource, iter) {
2816 DavResource *res = dav_get(sn, resource->path,
"D:getetag,idav:status,idav:version-collection,idav:split,`idav:content-hash`,idavprops:tags,idavprops:finfo,idavprops:xattributes,idavprops:link");
2817 if(!res) {
2818 log_printf(
"skip: %s\n", resource->path);
2819 continue;
2820 }
2821 char *status = dav_get_string_property(res,
"idav:status");
2822 if(status && !strcmp(status,
"broken")) {
2823 log_error(
"Resource %s broken\n", res->path);
2824 continue;
2825 }
2826
2827 DavResource *vres =
NULL;
2828 DavBool update_local_entry =
TRUE;
2829 if(version) {
2830 if(dir->versioning->type ==
VERSIONING_SIMPLE) {
2831 vres = versioning_simple_find(res, version);
2832 }
else if(dir->versioning->type ==
VERSIONING_DELTAV) {
2833 vres = versioning_deltav_find(res, version);
2834 }
2835 if(!vres) {
2836 log_error(
"Cannot find specified version for resource %s\n", res->path);
2837 ret =
1;
2838 break;
2839 }
2840
2841
2842
2843
2844
2845 update_local_entry =
FALSE;
2846 }
else {
2847 vres = res;
2848 }
2849
2850
2851 if(!sync_shutdown) {
2852 if(resource->isdirectory) {
2853 char *local_path = create_local_path(dir, res->path);
2854 if(sys_mkdir(local_path) && errno !=
EEXIST) {
2855 log_error(
2856 "Cannot create directory %s: %s",
2857 local_path, strerror(errno));
2858 }
2859 free(local_path);
2860 }
else {
2861 if(sync_get_resource(a, dir, res->path, vres, db, update_local_entry, &sync_success)) {
2862 log_error(
"sync_get_resource failed for resource: %s\n", res->path);
2863 sync_error++;
2864 }
else if(!update_local_entry) {
2865 LocalResource *lr = cxMapGet(db->resources, cx_hash_key_str(res->path));
2866 if(lr) {
2867 lr->last_modified =
0;
2868 nullfree(lr->hash);
2869 lr->hash =
NULL;
2870 }
2871 }
2872 }
2873 }
2874 }
2875 iter = cxListIterator(deleted);
2876 }
2877
2878
2879 if(locked) {
2880 if(dav_unlock(root)) {
2881 log_resource_error(sn,
"/");
2882 ret = -
1;
2883 }
else {
2884 locked =
FALSE;
2885 }
2886 }
2887
2888
2889 if(store_db(db, dir->database, dir->db_settings)) {
2890 log_error(
"Cannot store sync db\n");
2891 ret = -
2;
2892 }
2893
2894
2895 dav_session_destroy(sn);
2896
2897 if(!locked && locktokenfile) {
2898 remove(locktokenfile);
2899 }
2900
2901
2902 if(ret != -
2) {
2903 char *str_success = sync_success ==
1 ?
"file" :
"files";
2904 char *str_error = sync_error ==
1 ?
"error" :
"errors";
2905 log_printf(
"Result: %d %s pulled, %d %s\n",
2906 sync_success, str_success,
2907 sync_error, str_error);
2908 }
2909
2910 return ret;
2911 }
2912
2913 static void print_outgoging_file(LocalResource *res) {
2914 char *lastmodified = util_date_str(res->last_modified);
2915 char *size = util_size_str(
FALSE, res->size);
2916 log_printf(
" %-49s %12s %10s\n", res->path+
1, lastmodified, size);
2917 free(lastmodified);
2918 free(size);
2919 }
2920
2921 void print_outgoing(
2922 CmdArgs *args,
2923 CxList *ls_new,
2924 CxList *ls_modified,
2925 CxList *ls_conflict,
2926 CxList *ls_update,
2927 CxList *ls_delete,
2928 CxList *ls_move,
2929 CxList *ls_copy,
2930 CxList *ls_mkcol)
2931 {
2932 int64_t total_size =
0;
2933
2934 size_t len_new = ls_new->size;
2935 size_t len_mod = ls_modified->size;
2936 size_t len_cnf = ls_conflict->size;
2937 size_t len_upd = ls_update->size;
2938 size_t len_del = ls_delete->size;
2939 size_t len_mov = ls_move->size;
2940 size_t len_cpy = ls_copy->size;
2941 size_t len_mkc = ls_mkcol->size;
2942
2943 size_t total = len_new + len_mod + len_cnf + len_upd + len_del + len_mov + len_cpy + len_mkc;
2944 if(total ==
0) {
2945 log_printf(
"no changes\n");
2946 return;
2947 }
2948
2949 log_printf(
"%s\n",
"File Last Modified Size");
2950 log_printf(
"%s\n",
"==============================================================================");
2951
2952 if(ls_mkcol->size >
0) {
2953 log_printf(
"Directories:\n");
2954 CxIterator i = cxListIterator(ls_mkcol);
2955 cx_foreach(LocalResource *, res, i) {
2956 log_printf(
" %-49s\n", res->path+
1);
2957 total_size += res->size;
2958 }
2959 }
2960 if(ls_new->size >
0) {
2961 log_printf(
"New:\n");
2962 CxIterator i = cxListIterator(ls_new);
2963 cx_foreach(LocalResource *, res, i) {
2964 print_outgoging_file(res);
2965 total_size += res->size;
2966 }
2967 }
2968 if(ls_modified->size >
0) {
2969 log_printf(
"Modified:\n");
2970 CxIterator i = cxListIterator(ls_modified);
2971 cx_foreach(LocalResource *, res, i) {
2972 print_outgoging_file(res);
2973 total_size += res->size;
2974 }
2975 }
2976 if(ls_update->size >
0) {
2977 log_printf(
"Update:\n");
2978 CxIterator i = cxListIterator(ls_update);
2979 cx_foreach(LocalResource *, res, i) {
2980 char *lastmodified = util_date_str(res->last_modified);
2981 log_printf(
" %-49s %12s\n", res->path+
1, lastmodified);
2982 free(lastmodified);
2983 }
2984 }
2985 if(ls_delete->size >
0) {
2986 log_printf(
"Delete:\n");
2987 CxIterator i = cxListIterator(ls_delete);
2988 cx_foreach(LocalResource *, res, i) {
2989 log_printf(
" %s\n", res->path+
1);
2990 }
2991 }
2992 if(ls_copy->size >
0) {
2993 log_printf(
"Copy:\n");
2994 CxIterator i = cxListIterator(ls_copy);
2995 cx_foreach(LocalResource *, res, i) {
2996 log_printf(
"%s -> %s\n", res->origin->path+
1, res->path);
2997 }
2998 }
2999 if(ls_move->size >
0) {
3000 log_printf(
"Move:\n");
3001 CxIterator i = cxListIterator(ls_move);
3002 cx_foreach(LocalResource *, res, i) {
3003 log_printf(
"%s -> %s\n", res->origin->path+
1, res->path);
3004 }
3005 }
3006 if(ls_conflict->size >
0) {
3007 log_printf(
"Conflict\n");
3008 CxIterator i = cxListIterator(ls_conflict);
3009 cx_foreach(LocalResource *, res, i) {
3010 log_printf(
" %s\n", res->path+
1);
3011 }
3012 }
3013
3014 char *total_size_str = util_size_str(
FALSE, total_size);
3015
3016 log_printf(
"\n");
3017 if(len_new >
0) log_printf(
"new: %zu, ", len_new);
3018 if(len_mod >
0) log_printf(
"modified: %zu, ", len_mod);
3019 if(len_upd >
0) log_printf(
"updated: %zu, ", len_upd);
3020 if(len_cpy >
0) log_printf(
"copied: %zu, ", len_cpy);
3021 if(len_mov >
0) log_printf(
"moved: %zu, ", len_mov);
3022 if(len_del >
0) log_printf(
"deleted: %zu, ", len_del);
3023 if(len_cnf >
0) log_printf(
"conflicts: %zu, ", len_cnf);
3024 log_printf(
"total size: %s\n", total_size_str);
3025
3026 free(total_size_str);
3027 }
3028
3029 CxList* local_scan(SyncDirectory *dir, SyncDatabase *db) {
3030 CxList *resources = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
3031
3032 char *path = strdup(
"/");
3033 CxList *stack = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
3034 cxListInsert(stack,
0, path);
3035 while(stack->size >
0) {
3036
3037
3038
3039 char *p = cxListAt(stack,
0);
3040 cxListRemove(stack,
0);
3041 char *local_path = create_local_path(dir, p);
3042 SYS_DIR local_dir = sys_opendir(local_path);
3043
3044 if(!local_dir) {
3045 log_error(
"Cannot open directory %s: %s\n", local_path, strerror(errno));
3046 }
else {
3047 SysDirEnt *ent;
3048 while((ent = sys_readdir(local_dir)) !=
NULL) {
3049 if(!strcmp(ent->name,
".") || !strcmp(ent->name,
"..")) {
3050 continue;
3051 }
3052
3053 char *new_path = util_concat_path(p, ent->name);
3054 DavBool free_new_path =
TRUE;
3055 LocalResource *res = local_resource_new(dir, db, new_path);
3056 if(res) {
3057 if(res->isdirectory) {
3058 cxListAdd(resources, res);
3059 cxListInsert(stack,
0, new_path);
3060 free_new_path =
FALSE;
3061 }
else {
3062 cxListAdd(resources, res);
3063 }
3064 }
3065 if(free_new_path) {
3066 free(new_path);
3067 }
3068 }
3069 sys_closedir(local_dir);
3070
3071 }
3072 free(local_path);
3073 free(p);
3074 }
3075
3076 return resources;
3077 }
3078
3079
3080 LocalResource* local_resource_new(SyncDirectory *dir, SyncDatabase *db,
char *path) {
3081 char *file_path = create_local_path(dir, path);
3082 SYS_STAT s;
3083 if(sys_lstat(file_path, &s)) {
3084 log_error(
"Cannot stat file %s: %s\n", file_path, strerror(errno));
3085 free(file_path);
3086 return NULL;
3087 }
3088
3089 LocalResource *res = calloc(
1,
sizeof(LocalResource));
3090 res->mode = s.st_mode &
07777;
3091 res->uid = s.st_uid;
3092 res->gid = s.st_gid;
3093 res->last_modified = s.st_mtime;
3094 res->path = strdup(path);
3095 if(!
S_ISDIR(s.st_mode)) {
3096
3097 res->size = s.st_size;
3098 }
else {
3099
3100 res->isdirectory =
1;
3101 }
3102
3103 int skip_file =
0;
3104 if(
SYS_ISLINK(file_path, s)) {
3105 char *lnkbuf = sys_readlink(file_path, &s);
3106 #ifdef SYS_LINK_EXT
3107
3108
3109
3110
3111
3112
3113 cxstring fpath = cx_str(file_path);
3114 cxstring rpath = cx_str(path);
3115
3116 cxmutstr new_file_path = cx_strdup(cx_strsubsl(fpath,
0 , fpath.length-
4));
3117
3118 SYS_STAT nfp_s;
3119 if(!sys_stat(new_file_path.ptr, &nfp_s)) {
3120
3121
3122 free(lnkbuf);
3123 lnkbuf =
NULL;
3124 }
else {
3125 cxmutstr new_path = cx_strdup(cx_strsubsl(rpath,
0, rpath.length-
4));
3126 res->local_path = res->path;
3127 res->path = new_path.ptr;
3128 }
3129 free(new_file_path.ptr);
3130 #endif
3131
3132 if(lnkbuf) {
3133
3134 char *normalized =
NULL;
3135 if(!util_path_isabsolut(lnkbuf)) {
3136 char *link_parent = util_parent_path(res->path);
3137 char *abs_link_parent = util_concat_path(dir->path, link_parent);
3138 char *link = util_concat_path(abs_link_parent, lnkbuf);
3139 normalized = util_path_normalize(link);
3140 free(abs_link_parent);
3141 free(link_parent);
3142 free(link);
3143 }
else {
3144 normalized = util_path_normalize(lnkbuf);
3145 }
3146
3147 char *dirpath = util_path_normalize(dir->path);
3148 int isintern = util_path_isrelated(dirpath, normalized);
3149
3150
3151 if(
SYNC_SYMLINK(dir) && isintern) {
3152
3153 char *rel = util_create_relative_path(normalized, file_path);
3154 res->link_target = rel;
3155 }
else {
3156 #ifndef SYS_LINK_EXT
3157 SYS_STAT targetstat;
3158 if(!sys_stat(file_path, &targetstat)) {
3159 res->isdirectory =
S_ISDIR(targetstat.st_mode);
3160
3161 int nofollowextern = (dir->symlink &
SYNC_SYMLINK_IGNORE_EXTERN) ==
SYNC_SYMLINK_IGNORE_EXTERN;
3162 int nofollowintern = (dir->symlink &
SYNC_SYMLINK_IGNORE_INTERN) ==
SYNC_SYMLINK_IGNORE_INTERN;
3163 if(isintern && nofollowintern) {
3164 skip_file =
TRUE;
3165 }
else if(!isintern && nofollowextern) {
3166 skip_file =
TRUE;
3167 }
3168 }
else {
3169
3170 skip_file =
TRUE;
3171 }
3172 #endif
3173 }
3174 free(dirpath);
3175 free(normalized);
3176 free(lnkbuf);
3177 }
3178 }
3179
3180 if(!res->isdirectory && dir->push_strategy ==
PUSH_STRATEGY_HASH) {
3181 res->hash = util_file_hash(file_path);
3182 }
3183
3184 free(file_path);
3185
3186 if(skip_file) {
3187 local_resource_free(res);
3188 res =
NULL;
3189 }
3190
3191 return res;
3192 }
3193
3194 char* local_resource_path(LocalResource *res) {
3195 return res->local_path ? res->local_path : res->path;
3196 }
3197
3198 int local_resource_is_changed(
3199 SyncDirectory *dir,
3200 SyncDatabase *db,
3201 LocalResource *res,
3202 CxMap *svrres,
3203 DavBool restore_removed,
3204 DavBool restore_modified)
3205 {
3206 LocalResource *db_res = cxMapGet(db->resources, cx_hash_key_str(res->path));
3207 res->tags_updated =
0;
3208 if(db_res) {
3209
3210 res->tags_updated = db_res->tags_updated;
3211 if(db_res->etag) {
3212 res->etag = strdup(db_res->etag);
3213 }
3214 if(db_res->tags_hash) {
3215 res->tags_hash = strdup(db_res->tags_hash);
3216 }
3217 if(db_res->remote_tags_hash) {
3218 res->remote_tags_hash = strdup(db_res->remote_tags_hash);
3219 }
3220 if(db_res->xattr_hash) {
3221 res->xattr_hash = strdup(db_res->xattr_hash);
3222 }
3223 if(db_res->hash) {
3224 res->prev_hash = strdup(db_res->hash);
3225 }
3226 if(db_res->link_target) {
3227 res->link_target_db = db_res->link_target;
3228 }
3229 res->versioncontrol = db_res->versioncontrol;
3230
3231
3232
3233 if(db_res->parts) {
3234 local_resource_copy_parts(db_res, res);
3235 }
3236
3237
3238 if(svrres) {
3239 DavResource *remote = cxMapGet(svrres, cx_hash_key_str(res->path));
3240 if(restore_removed && !remote) {
3241 return 1;
3242 }
3243 if(!res->isdirectory && restore_modified && remote) {
3244 char *etag = dav_get_string_property(remote,
"D:getetag");
3245 if(!etag || (db_res->etag && strcmp(etag, db_res->etag))) {
3246 res->restore =
TRUE;
3247 return 1;
3248 }
3249 }
3250 }
3251
3252
3253
3254
3255
3256
3257 if(db_res->tags_updated) {
3258 res->tags_updated =
1;
3259 res->metadata_updated =
1;
3260 }
else if(dir->tagconfig && dir->tagconfig->detect_changes ) {
3261 CxBuffer *tags = sync_get_file_tag_data(dir, res);
3262 if(tags) {
3263 if(db_res->tags_hash) {
3264 char *hash = dav_create_hash(tags->space, tags->size);
3265 if(strcmp(hash, db_res->tags_hash)) {
3266 res->tags_updated =
1;
3267 }
3268 free(hash);
3269 }
else {
3270 res->tags_updated =
1;
3271 }
3272 }
else if(db_res->tags_hash) {
3273 res->tags_updated =
1;
3274 }
3275 res->metadata_updated = res->tags_updated;
3276 }
3277
3278
3279 if((dir->metadata &
FINFO_MTIME) ==
FINFO_MTIME) {
3280 if(db_res->last_modified != res->last_modified) {
3281 res->finfo_updated =
1;
3282 res->metadata_updated =
1;
3283 }
3284 }
3285
3286 if((dir->metadata &
FINFO_MODE) ==
FINFO_MODE) {
3287 if(db_res->mode != res->mode) {
3288 res->finfo_updated =
1;
3289 res->metadata_updated =
1;
3290 }
3291 }
3292
3293 if((dir->metadata &
FINFO_OWNER) ==
FINFO_OWNER) {
3294 if(db_res->uid != res->uid || db_res->gid != res->gid) {
3295 res->finfo_updated =
1;
3296 res->metadata_updated =
1;
3297 }
3298 }
3299
3300
3301 if((dir->metadata &
FINFO_XATTR) ==
FINFO_XATTR) {
3302 char *path = create_local_path(dir, local_resource_path(db_res));
3303 XAttributes *xattr = file_get_attributes(path, (xattr_filter_func)xattr_filter, dir);
3304
3305 if((db_res->xattr_hash && !xattr) ||
3306 (!db_res->xattr_hash && xattr) ||
3307 (xattr && db_res->xattr_hash && strcmp(xattr->hash, db_res->xattr_hash)))
3308 {
3309 res->metadata_updated =
1;
3310 res->xattr_updated =
1;
3311 res->xattr = xattr;
3312 }
else if(xattr) {
3313 xattributes_free(xattr);
3314 }
3315 }
3316
3317
3318
3319
3320
3321 if(nullstrcmp(db_res->link_target, res->link_target)) {
3322 res->link_updated =
1;
3323 }
else {
3324 if(db_res->hash && res->hash) {
3325
3326 if(!strcmp(db_res->hash, res->hash)) {
3327 return 0;
3328 }
3329 }
else if(
3330 db_res->last_modified == res->last_modified &&
3331 db_res->size == res->size &&
3332 db_res->isdirectory == res->isdirectory)
3333 {
3334
3335 return 0;
3336 }
else if(
SYNC_HASHING(dir)) {
3337
3338 char *local_path = util_concat_path(dir->path, local_resource_path(res));
3339 res->hash = util_file_hash(local_path);
3340 free(local_path);
3341
3342
3343 if(res->hash && db_res->hash && !strcmp(res->hash, db_res->hash)) {
3344 return 0;
3345 }
3346 }
3347 }
3348 }
else {
3349 res->tags_updated =
1;
3350 res->finfo_updated =
1;
3351 res->xattr_updated =
1;
3352 res->metadata_updated =
1;
3353 res->isnew =
1;
3354 }
3355 return 1;
3356 }
3357
3358 int remote_resource_is_changed(
3359 DavSession *sn,
3360 SyncDirectory *dir,
3361 SyncDatabase *db,
3362 DavResource *remote,
3363 LocalResource *res,
3364 DavBool *equal)
3365 {
3366 if(equal) {
3367 *equal =
FALSE;
3368 }
3369
3370 DavPropName properties[] = {
3371 {
"DAV:",
"getetag"},
3372 {
DAV_NS,
"version-collection"},
3373 {
DAV_NS,
"content-hash"},
3374 {
DAV_NS,
"split" },
3375 {
DAV_PROPS_NS,
"tags"},
3376 {
DAV_PROPS_NS,
"link" }
3377 };
3378 int err = dav_load_prop(remote, properties,
6);
3379
3380 if(res->restore) {
3381 return 0;
3382 }
3383
3384 if(remote->iscollection && !res->parts) {
3385 return 1;
3386 }
3387
3388 char *link = dav_get_string_property_ns(remote,
DAV_PROPS_NS,
"link");
3389
3390 int ret =
0;
3391 if(err ==
0) {
3392 char *etag = dav_get_string_property(remote,
"D:getetag");
3393 char *hash = sync_get_content_hash(remote);
3394
3395 if(res->link_target_db || link) {
3396 ret = nullstrcmp(res->link_target_db, link);
3397 if(ret && equal) {
3398 *equal = !nullstrcmp(res->link_target, link);
3399 }
3400 return ret;
3401 }
3402
3403 if(hash && res->hash && equal) {
3404
3405 if(!strcmp(hash, res->hash)) {
3406 *equal =
TRUE;
3407 return 0;
3408 }
3409 }
3410
3411 if(hash && res->prev_hash) {
3412 if(strcmp(hash, res->prev_hash)) {
3413 ret =
1;
3414 }
3415 }
else if(!res->etag) {
3416
3417 ret =
1;
3418 }
else if(etag) {
3419 cxstring e = cx_str(etag);
3420 if(cx_strprefix(e,
CX_STR(
"W/"))) {
3421 e = cx_strsubs(e,
2);
3422 }
3423 if(strcmp(e.ptr, res->etag)) {
3424 ret =
1;
3425 }
3426 }
else {
3427
3428 fprintf(stderr,
"Warning: resource %s has no etag\n", remote->href);
3429 }
3430 }
3431 return ret;
3432 }
3433
3434 int local_resource_load_metadata(SyncDirectory *dir, LocalResource *res) {
3435
3436 if((dir->metadata &
FINFO_XATTR) ==
FINFO_XATTR) {
3437 char *path = create_local_path(dir, local_resource_path(res));
3438 XAttributes *xattr = file_get_attributes(path, (xattr_filter_func)xattr_filter, dir);
3439 res->xattr = xattr;
3440 free(path);
3441 }
3442
3443 return 0;
3444 }
3445
3446 void local_resource_set_etag(LocalResource *local,
const char *etag) {
3447
3448 if(local->etag) {
3449 free(local->etag);
3450 }
3451
3452 if(!etag) {
3453 local->etag =
NULL;
3454 return;
3455 }
3456
3457 cxstring e = cx_str(etag);
3458 if(cx_strprefix(e,
CX_STR(
"W/"))) {
3459 e = cx_strsubs(e,
2);
3460 }
3461 local->etag = cx_strdup(e).ptr;
3462 }
3463
3464 char* resource_local_path(DavResource *res) {
3465 cxstring path = cx_str(res->path);
3466 if(path.length >
0 && path.ptr[path.length-
1] ==
'/') {
3467 path.length--;
3468 }
3469 #ifdef SYS_LINK_EXT
3470
3471 if(dav_get_property_ns(res,
DAV_PROPS_NS,
"link")) {
3472 return cx_asprintf(
"%.*s%s", (
int)path.length, path.ptr,
SYS_LINK_EXT).ptr;
3473 }
else {
3474
3475 return cx_strdup(path).ptr;
3476 }
3477 #else
3478 return cx_strdup(path).ptr;
3479 #endif
3480 }
3481
3482 size_t resource_get_blocksize(SyncDirectory *dir, LocalResource *local, DavResource *res,
off_t filesize) {
3483 size_t local_blocksize =
0;
3484 if(local->blocksize <
0) {
3485
3486 return 0;
3487 }
else if(local->blocksize >
0) {
3488 local_blocksize = (
size_t)local->blocksize;
3489 }
else if(dir->splitconfig) {
3490 CxIterator i = cxListIterator(dir->splitconfig);
3491 cx_foreach(SplitConfig *, sc, i) {
3492 if(sc->filter) {
3493 if(res_matches_filter(sc->filter, local->path)) {
3494 continue;
3495 }
3496 }
3497
3498 if(sc->minsize >
0) {
3499 if(filesize < sc->minsize) {
3500 continue;
3501 }
3502 }
3503
3504 local_blocksize = sc->blocksize;
3505 break;
3506 }
3507 }
3508
3509 size_t svr_blocksize =
0;
3510 char *svr_blocksize_str = dav_get_string_property_ns(res,
DAV_NS,
"split");
3511 if(svr_blocksize_str) {
3512 uint64_t i =
0;
3513 if(util_strtouint(svr_blocksize_str, &i)) {
3514 svr_blocksize = (
size_t)i;
3515 }
3516 }
3517
3518 if(local_blocksize >
0 && svr_blocksize >
0 && local_blocksize != svr_blocksize) {
3519 fprintf(stderr,
"Warning: Blocksize mismatch: %s: local: %zu server: %zu\n", local->path, local_blocksize, svr_blocksize);
3520 return svr_blocksize;
3521 }
else if(local_blocksize >
0) {
3522 return local_blocksize;
3523 }
else if(svr_blocksize >
0) {
3524 return svr_blocksize;
3525 }
3526
3527 return 0;
3528
3529 }
3530
3531 int resource_pathlen_cmp(LocalResource *res1, LocalResource *res2,
void *n) {
3532 size_t s1 = strlen(res1->path);
3533 size_t s2 = strlen(res2->path);
3534 if(s1 < s2) {
3535 return 1;
3536 }
else if(s1 > s2) {
3537 return -
1;
3538 }
else {
3539 return 0;
3540 }
3541 }
3542
3543 int resource_path_cmp(LocalResource *res1, LocalResource *res2,
void *n) {
3544 return strcmp(res1->path, res2->path);
3545 }
3546
3547
3548 DavResource *versioning_simple_find(DavResource *res,
const char *version) {
3549 char *vcol_href = dav_get_string_property_ns(res,
DAV_NS,
VERSION_PATH_PROPERTY);
3550 if(!vcol_href) {
3551 return NULL;
3552 }
3553 DavResource *vcol = dav_resource_new_href(res->session, vcol_href);
3554 if(!vcol) {
3555 return NULL;
3556 }
3557
3558
3559 if(dav_load_prop(vcol, defprops, numdefprops)) {
3560 log_resource_error(res->session, vcol->path);
3561 dav_resource_free(vcol);
3562 return NULL;
3563 }
3564
3565 DavResource *ret =
NULL;
3566 DavResource *child = vcol->children;
3567 while(child) {
3568 DavResource *next = child->next;
3569 if(!strcmp(child->name, version)) {
3570 ret = child;
3571 }
else {
3572 dav_resource_free(child);
3573 }
3574 child = next;
3575 }
3576 dav_resource_free(vcol);
3577
3578 return ret;
3579 }
3580
3581
3582 DavResource* versioning_deltav_find(DavResource *res,
const char *version) {
3583 DavResource *list = dav_versiontree(res,
"D:getetag,idav:status,idav:split,idavprops:link,idavprops:finfo,idavprops:xattributes,idavprops:tags");
3584 DavResource *ret =
NULL;
3585 while(list) {
3586 DavResource *next = list->next;
3587 if(!ret) {
3588 char *vname = dav_get_string_property(list,
"D:version-name");
3589 if(vname && !strcmp(vname, version)) {
3590 ret = list;
3591 }
3592 }
3593 if(list != ret) {
3594 dav_resource_free(list);
3595 }
3596 list = next;
3597 }
3598 return ret;
3599 }
3600
3601 int sync_set_status(DavResource *res,
char *status) {
3602 DavResource *resource = dav_resource_new(res->session, res->path);
3603 dav_set_string_property(resource,
"idav:status", status);
3604 int ret = dav_store(resource);
3605 dav_resource_free(resource);
3606 return ret;
3607 }
3608
3609 int sync_remove_status(DavResource *res) {
3610 DavResource *resource = dav_resource_new(res->session, res->path);
3611 dav_remove_property(resource,
"idav:status");
3612 int ret = dav_store(resource);
3613 dav_resource_free(resource);
3614 return ret;
3615 }
3616
3617 int sync_store_metadata(SyncDirectory *dir,
const char *path, LocalResource *local, DavResource *res) {
3618 int ret =
0;
3619
3620 DavXmlNode *fileinfo = dav_get_property_ns(res,
DAV_PROPS_NS,
"finfo");
3621 if(fileinfo) {
3622 FileInfo f;
3623 finfo_get_values(fileinfo, &f);
3624 if((dir->metadata &
FINFO_MTIME) ==
FINFO_MTIME && f.date_set) {
3625
3626 #ifndef _WIN32
3627
3628 struct utimbuf t;
3629 t.actime = f.last_modified;
3630 t.modtime = f.last_modified;
3631 if(utime(path, &t)) {
3632 log_error(
"utime failed for file: %s : %s\n", path, strerror(errno));
3633 ret =
1;
3634 }
else {
3635 local->last_modified = f.last_modified;
3636 }
3637 #else
3638 local->last_modified =
0;
3639 #endif
3640 }
3641 if((dir->metadata &
FINFO_MODE) ==
FINFO_MODE && f.mode_set) {
3642
3643 if(chmod(path, f.mode)) {
3644 log_error(
"chmod failed for file: %s : %s\n", path, strerror(errno));
3645 ret =
1;
3646 }
else {
3647 local->mode = f.mode;
3648 }
3649 }
3650 }
3651
3652 if((dir->metadata &
FINFO_XATTR) ==
FINFO_XATTR) {
3653 DavXmlNode *xattr_prop = dav_get_property_ns(res,
DAV_PROPS_NS,
"xattributes");
3654 XAttributes *xattr =
NULL;
3655 if(xattr_prop) {
3656 xattr = xml_get_attributes(xattr_prop);
3657 }
3658 if(!sync_store_xattr(dir, path, xattr)) {
3659 if(local->xattr_hash) {
3660 free(local->xattr_hash);
3661 }
3662 local->xattr_hash = xattr ? xattr->hash :
NULL;
3663 }
3664 }
3665
3666 if(sync_store_tags(dir, path, local, res)) {
3667 ret =
1;
3668 }
3669
3670 return ret;
3671 }
3672
3673 int sync_store_xattr(SyncDirectory *dir,
const char *path, XAttributes *xattr) {
3674
3675 ssize_t nelm =
0;
3676 char **list = xattr_list(path, &nelm);
3677 CxMap *current_xattr =
NULL;
3678 if(nelm >
0) {
3679 current_xattr = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, nelm +
8);
3680 for(
int i=
0;i<nelm;i++) {
3681
3682 cxMapPut(current_xattr, cx_hash_key_str(list[i]), list[i]);
3683 }
3684 }
3685 if(list) {
3686 free(list);
3687 }
3688
3689
3690 size_t nattr = xattr ? xattr->nattr :
0;
3691 for(
int i=
0;i<nattr;i++) {
3692 cxmutstr value = xattr->values[i];
3693 if(xattr_set(path, xattr->names[i], value.ptr, value.length)) {
3694 log_error(
3695 "Cannot store xattr ''%s'' for file: %s\n",
3696 xattr->names[i],
3697 path);
3698 }
3699
3700 if(current_xattr) {
3701
3702
3703
3704 char *value = cxMapRemoveAndGet(current_xattr, cx_hash_key_str(xattr->names[i]));
3705 if(value) {
3706 free(value);
3707 }
3708 }
3709 }
3710
3711 if(current_xattr) {
3712 CxIterator i = cxMapIteratorValues(current_xattr);
3713 char *value =
NULL;
3714 cx_foreach(
char *, value, i) {
3715 (
void)xattr_remove(path, value);
3716 free(value);
3717 }
3718 cxMapDestroy(current_xattr);
3719 }
3720
3721 return 0;
3722 }
3723
3724 int sync_store_tags(SyncDirectory *dir,
const char *path, LocalResource *local, DavResource *res) {
3725 if(!dir->tagconfig) {
3726 return 0;
3727 }
3728
3729 char *remote_hash =
NULL;
3730 CxList *tags =
NULL;
3731 if(dir->tagconfig) {
3732 DavXmlNode *tagsprop = dav_get_property_ns(res,
DAV_PROPS_NS,
"tags");
3733 if(tagsprop) {
3734 tags = parse_dav_xml_taglist(tagsprop);
3735 remote_hash = create_tags_hash(tags);
3736 }
3737 }
3738
3739 DavBool store_tags =
FALSE;
3740 DavBool tags_changed =
FALSE;
3741 CxList *local_tags = sync_get_file_tags(dir, local, &tags_changed,
NULL);
3742 if(tags_changed) {
3743 switch(dir->tagconfig->conflict) {
3744 case TAG_NO_CONFLICT: {
3745 store_tags =
TRUE;
3746 break;
3747 }
3748 case TAG_KEEP_LOCAL: {
3749 store_tags =
FALSE;
3750 break;
3751 }
3752 case TAG_KEEP_REMOTE: {
3753 store_tags =
TRUE;
3754 local->tags_updated =
FALSE;
3755 break;
3756 }
3757 case TAG_MERGE: {
3758 CxList *new_tags = merge_tags(local_tags, tags);
3759
3760 tags = new_tags;
3761 store_tags =
TRUE;
3762
3763 local->tags_updated =
TRUE;
3764 break;
3765 }
3766 }
3767 }
else {
3768 if(!compare_taglists(tags, local_tags)) {
3769 store_tags =
TRUE;
3770 }
3771
3772 }
3773
3774 if(!store_tags) {
3775 nullfree(local->remote_tags_hash);
3776 local->remote_tags_hash = remote_hash;
3777 return 0;
3778 }
3779
3780 int ret = sync_store_tags_local(dir, local, path, tags);
3781 if(!ret) {
3782 if(local->remote_tags_hash) {
3783 free(local->remote_tags_hash);
3784 }
3785 local->remote_tags_hash = remote_hash;
3786 }
3787
3788
3789
3790 return ret;
3791 }
3792
3793 int sync_store_tags_local(SyncDirectory *dir, LocalResource *local,
const char *path, CxList *tags) {
3794 int ret =
0;
3795 if(dir->tagconfig->store ==
TAG_STORE_XATTR) {
3796 CxBuffer *data =
NULL;
3797 if(tags) {
3798 switch(dir->tagconfig->local_format) {
3799 default:
break;
3800 case TAG_FORMAT_TEXT: {
3801 data = create_text_taglist(tags);
3802 break;
3803 }
3804 case TAG_FORMAT_CSV: {
3805 data = create_csv_taglist(tags);
3806 break;
3807 }
3808 case TAG_FORMAT_MACOS: {
3809 data = create_macos_taglist(tags);
3810 break;
3811 }
3812 }
3813
3814 if(data) {
3815 char *data_hash = dav_create_hash(data->space, data->size);
3816 int update =
1;
3817 if(local) {
3818 if(!local->tags_hash || strcmp(data_hash, local->tags_hash)) {
3819
3820 }
else {
3821 update =
0;
3822 }
3823 }
3824 if(update) {
3825 ret = xattr_set(path, dir->tagconfig->xattr_name, data->space, data->pos);
3826 if(local) {
3827 if(local->tags_hash) {
3828 free(local->tags_hash);
3829 local->tags_hash =
NULL;
3830 }
3831 local->tags_hash = data_hash;
3832 }
3833 }
else {
3834 free(data_hash);
3835 }
3836 cxBufferFree(data);
3837 }
else {
3838 ret = -
1;
3839 }
3840 }
else {
3841 if(local) {
3842
3843 }
3844
3845 xattr_remove(path, dir->tagconfig->xattr_name);
3846 }
3847 }
3848
3849 if(!ret && local) {
3850 local->tags_updated =
0;
3851 }
3852
3853 return ret;
3854 }
3855
3856 CxBuffer* sync_get_file_tag_data(SyncDirectory *dir, LocalResource *res) {
3857 if(!dir->tagconfig) {
3858 return NULL;
3859 }
3860 if(res->cached_tags) {
3861 return res->cached_tags;
3862 }
3863 CxBuffer *buf =
NULL;
3864 if(dir->tagconfig->store ==
TAG_STORE_XATTR) {
3865 ssize_t tag_length =
0;
3866 char *local_path = create_local_path(dir, local_resource_path(res));
3867 char* tag_data = xattr_get(
3868 local_path,
3869 dir->tagconfig->xattr_name,
3870 &tag_length);
3871 free(local_path);
3872
3873 if(tag_length >
0) {
3874 buf = cxBufferCreate(tag_data, (
size_t)tag_length, cxDefaultAllocator,
CX_BUFFER_FREE_CONTENTS);
3875 buf->size = (
size_t)tag_length;
3876 }
3877 }
3878 res->cached_tags = buf;
3879 return buf;
3880 }
3881
3882 CxList* sync_get_file_tags(SyncDirectory *dir, LocalResource *res, DavBool *changed,
char **newhash) {
3883 if(changed) *changed =
FALSE;
3884
3885 CxList *tags =
NULL;
3886
3887 if(!res) {
3888 return NULL;
3889 }
3890
3891 if(!dir->tagconfig) {
3892 return NULL;
3893 }
3894 if(changed && res->tags_updated) {
3895 *changed =
TRUE;
3896 }
3897 if(dir->tagconfig->store ==
TAG_STORE_XATTR) {
3898 CxBuffer *tag_buf = res->cached_tags ?
3899 res->cached_tags :
3900 sync_get_file_tag_data(dir, res);
3901
3902 if(tag_buf) {
3903 char *new_hash = dav_create_hash(tag_buf->space, tag_buf->size);
3904 if(res->tags_hash) {
3905 if(changed && strcmp(res->tags_hash, new_hash)) {
3906 *changed =
TRUE;
3907 }
3908 free(res->tags_hash);
3909 res->tags_hash =
NULL;
3910 }
else {
3911 if(changed) *changed =
TRUE;
3912 }
3913 if(newhash) {
3914 *newhash = new_hash;
3915 }
else {
3916 free(new_hash);
3917 }
3918
3919 switch(dir->tagconfig->local_format) {
3920 default:
break;
3921 case TAG_FORMAT_TEXT: {
3922 tags = parse_text_taglist(tag_buf->space, tag_buf->size);
3923 break;
3924 }
3925 case TAG_FORMAT_CSV: {
3926 tags = parse_csv_taglist(tag_buf->space, tag_buf->size);
3927 break;
3928 }
3929 case TAG_FORMAT_MACOS: {
3930 tags = parse_macos_taglist(tag_buf->space, tag_buf->size);
3931 break;
3932 }
3933 }
3934 res->cached_tags = tag_buf;
3935 }
else if(res->tags_hash) {
3936 if(changed) *changed =
TRUE;
3937 }
3938 }
3939
3940 return tags;
3941 }
3942
3943 static int file_seek(
FILE *f,
curl_off_t offset,
int origin) {
3944 int ret = fseek(f, offset, origin);
3945 return ret ==
0 ?
CURL_SEEKFUNC_OK :
CURL_SEEKFUNC_CANTSEEK;
3946 }
3947
3948 size_t myread(
void *ptr,
size_t size,
size_t nmemb,
FILE *f) {
3949 size_t ret = fread(ptr, size, nmemb, f);
3950 return ret;
3951 }
3952
3953 int gen_random_name(
char *buf,
size_t len) {
3954 unsigned char name_prefix[
8];
3955 memset(name_prefix,
0,
8);
3956 dav_rand_bytes(name_prefix,
8);
3957 char *pre = util_hexstr(name_prefix,
8);
3958 int64_t ts = (
int64_t)time(
NULL);
3959 int w = snprintf(buf, len,
"%""-%s"PRId64, ts, pre);
3960 free(pre);
3961 return w >= len;
3962 }
3963
3964 #define VBEGIN_ERROR_MKCOL 1
3965 #define VBEGIN_ERROR_MOVE 2
3966 #define VBEGIN_ERROR_PROPPATCH 3
3967 #define VBEGIN_ERROR_CHECKOUT 4
3968 int versioning_begin(SyncDirectory *dir, DavResource *res,
int *exists,
int *versionized) {
3969 int ret =
0;
3970 *versionized =
0;
3971
3972 if(dir->versioning->type ==
VERSIONING_SIMPLE && res->exists) {
3973 DavResource *history_collection = dav_resource_new(
3974 res->session,
3975 dir->versioning->collection);
3976
3977
3978
3979
3980
3981
3982 char *history_href =
NULL;
3983 char *vcol_path = dav_get_string_property_ns(res,
DAV_NS,
VERSION_PATH_PROPERTY);
3984 if(!vcol_path) {
3985 DavResource *history_res =
NULL;
3986
3987
3988
3989 while(!history_res) {
3990 char history_res_name[
128];
3991 gen_random_name(history_res_name,
128);
3992
3993 history_res = dav_resource_new_child(
3994 res->session,
3995 history_collection,
3996 history_res_name);
3997 if(dav_exists(history_res)) {
3998 dav_resource_free(history_res);
3999 history_res =
NULL;
4000 }
4001 }
4002
4003 history_res->iscollection =
TRUE;
4004 if(dav_create(history_res)) {
4005 dav_resource_free(history_res);
4006 dav_resource_free(history_collection);
4007 return VBEGIN_ERROR_MKCOL;
4008 }
4009
4010 history_href = strdup(history_res->href);
4011
4012 dav_resource_free(history_res);
4013 }
else {
4014 history_href = vcol_path;
4015 }
4016
4017
4018 DavResource *version_res =
NULL;
4019 while(!version_res) {
4020 char version_name[
128];
4021 gen_random_name(version_name,
128);
4022
4023 char *href = util_concat_path(history_href, version_name);
4024 version_res = dav_resource_new_href(res->session, href);
4025 free(href);
4026
4027 char *dest = util_get_url(res->session, version_res->href);
4028 int err = dav_moveto(res, dest,
FALSE);
4029 free(dest);
4030
4031 if(err) {
4032 dav_resource_free(version_res);
4033 version_res =
NULL;
4034 if(res->session->error !=
DAV_PRECONDITION_FAILED) {
4035 ret =
VBEGIN_ERROR_MOVE;
4036 break;
4037 }
4038 }
else {
4039
4040
4041 *exists =
0;
4042 }
4043 }
4044
4045 if(!ret) {
4046 *versionized =
1;
4047
4048 dav_set_string_property_ns(version_res,
DAV_NS,
"origin", res->href);
4049 if(dav_store(version_res)) {
4050 ret =
VBEGIN_ERROR_PROPPATCH;
4051 }
4052 dav_resource_free(version_res);
4053
4054
4055
4056 dav_set_string_property_ns(
4057 res,
4058 DAV_NS,
4059 VERSION_PATH_PROPERTY,
4060 history_href);
4061 }
4062
4063 if(vcol_path != history_href) {
4064 free(history_href);
4065 }
4066
4067 dav_resource_free(history_collection);
4068 }
else if(dir->versioning->type ==
VERSIONING_DELTAV && res->exists){
4069
4070 if(dav_checkout(res)) {
4071 ret =
VBEGIN_ERROR_CHECKOUT;
4072 }
else {
4073 *versionized =
1;
4074 }
4075 }
4076
4077 return ret;
4078 }
4079
4080 int versioning_init(SyncDirectory *dir, LocalResource *local, DavResource *res) {
4081 if(local->versioncontrol) {
4082 return 0;
4083 }
4084 int ret =
0;
4085 if(dir->versioning->type ==
VERSIONING_DELTAV) {
4086 if(dav_versioncontrol(res)) {
4087 ret =
1;
4088 }
else {
4089 local->versioncontrol =
1;
4090 }
4091 }
4092 return ret;
4093 }
4094
4095 int versioning_end(SyncDirectory *dir, DavResource *res) {
4096 if(dir->versioning->type ==
VERSIONING_DELTAV) {
4097 return dav_checkin(res);
4098 }
else {
4099 return 0;
4100 }
4101 }
4102
4103 int versioning_delete_begin(SyncDirectory *dir, DavResource *res,
int *exists,
int *versionized) {
4104 *versionized =
0;
4105 if(dir->versioning->type ==
VERSIONING_SIMPLE) {
4106 versioning_begin(dir, res, exists, versionized);
4107 }
else {
4108
4109 *exists =
1;
4110 }
4111 return 0;
4112 }
4113
4114 int versioning_delete_end(SyncDirectory *dir, DavResource *res) {
4115 return 0;
4116 }
4117
4118 static void update_metadata_hashes(LocalResource *local, MetadataHashes hashes) {
4119 if(hashes.update_tags) {
4120 if(local->tags_hash) {
4121 free(local->tags_hash);
4122 local->tags_hash =
NULL;
4123 }
4124 local->tags_hash = hashes.tags;
4125 }
4126 if(hashes.update_tags_remote) {
4127 if(local->remote_tags_hash) {
4128 free(local->remote_tags_hash);
4129 }
4130 local->remote_tags_hash = hashes.tags_remote;
4131 }
4132 if(hashes.update_xattr) {
4133 if(local->xattr_hash) {
4134 free(local->xattr_hash);
4135 }
4136 local->xattr_hash = hashes.xattr;
4137 }
4138 }
4139
4140
4141 #define LOG10 log10
4142
4143 static CxList* upload_parts(
4144 LocalResource *local,
4145 DavResource *res,
4146 FILE *in,
4147 uint64_t filesize,
4148 size_t blocksize,
4149 uint64_t *blockcount,
4150 int *err)
4151 {
4152
4153
4154 if(res->exists) {
4155 if(!res->iscollection) {
4156 if(dav_delete(res)) {
4157 log_resource_error(res->session, res->path);
4158 *err =
1;
4159 return NULL;
4160 }
4161 res->exists =
0;
4162 return upload_parts(local, res, in, filesize, blocksize, blockcount, err);
4163 }
4164 }
else {
4165 res->iscollection =
1;
4166 if(dav_create(res)) {
4167 log_resource_error(res->session, res->path);
4168 *err =
1;
4169 return NULL;
4170 }
4171 }
4172 res->exists =
1;
4173
4174 if(!res->href) {
4175
4176 log_error(
"href is NULL\n");
4177 *err =
1;
4178 return NULL;
4179 }
4180
4181 char *buffer = malloc(blocksize);
4182 if(!buffer) {
4183 fprintf(stderr,
"Out of memory\n");
4184 *err =
1;
4185 return NULL;
4186 }
4187
4188
4189
4190 int nblocks = filesize / blocksize;
4191 int digits =
LOG10((
double)nblocks) +
1;
4192 if(digits >
127) {
4193 log_error(
"Too many parts\n");
4194 *err =
1;
4195 free(buffer);
4196 return NULL;
4197 }
4198
4199 CxMap *updated_parts_map = cxHashMapCreate(cxDefaultAllocator,
CX_STORE_POINTERS, (nblocks/
2)+
64);
4200 updated_parts_map->simple_destructor = (cx_destructor_func)filepart_free;
4201
4202 int blockindex =
0;
4203 int uploaded_parts =
0;
4204 size_t r;
4205
4206
4207
4208 uint32_t session_flags = res->session->flags;
4209 res->session->flags ^=
DAV_SESSION_ENCRYPT_NAME;
4210
4211 DAV_SHA_CTX *sha = dav_hash_init();
4212
4213 while((r = fread(buffer,
1, blocksize, in)) >
0) {
4214 dav_hash_update(sha, buffer, r);
4215
4216 int upload_block =
0;
4217 char *block_hash = dav_create_hash(buffer, r);
4218 if(blockindex >= local->numparts) {
4219
4220 upload_block =
1;
4221 }
else {
4222 FilePart part = local->parts[blockindex];
4223 if(!strcmp(part.hash, block_hash)) {
4224
4225 free(block_hash);
4226 block_hash =
NULL;
4227 }
else {
4228
4229 upload_block =
1;
4230 }
4231 }
4232
4233 if(upload_block) {
4234 char name[
128];
4235 snprintf(name,
128,
"%0*d", digits, blockindex);
4236
4237 char *part_href = util_concat_path(res->href, name);
4238 DavResource *part = dav_resource_new_href(res->session, part_href);
4239 free(part_href);
4240
4241
4242 dav_set_content_data(part, buffer, r);
4243 if(dav_store(part)) {
4244 *err =
1;
4245 log_resource_error(res->session, part->path);
4246 }
else {
4247
4248
4249
4250
4251 FilePart *f = calloc(
1,
sizeof(FilePart));
4252 f->block = blockindex;
4253 f->hash = block_hash;
4254 cxMapPut(updated_parts_map, cx_hash_key_str(name), f);
4255 }
4256 dav_resource_free(part);
4257 uploaded_parts++;
4258 }
4259 if(*err) {
4260 break;
4261 }
4262 blockindex++;
4263 }
4264 *blockcount = blockindex;
4265
4266
4267 res->session->flags = session_flags;
4268
4269 free(buffer);
4270 if(*err) {
4271 cxMapDestroy(updated_parts_map);
4272 return NULL;
4273 }
4274
4275
4276 unsigned char content_hash[
DAV_SHA256_DIGEST_LENGTH];
4277 dav_hash_final(sha, content_hash);
4278 sync_set_content_hash(res, content_hash);
4279 local->hash = util_hexstr(content_hash,
DAV_SHA256_DIGEST_LENGTH);
4280
4281
4282
4283 CxList *updated_parts = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
4284 DavResource *parts = dav_query(res->session,
"select D:getetag from %s order by name", res->path);
4285 if(!parts) {
4286 log_resource_error(res->session, parts->path);
4287 *err =
1;
4288 cxMapDestroy(updated_parts_map);
4289 return NULL;
4290 }
4291 DavResource *part = parts->children;
4292 while(part) {
4293 FilePart *fp = cxMapRemoveAndGet(updated_parts_map, cx_hash_key_str(part->name));
4294
4295
4296 if(fp) {
4297 char *etag = dav_get_string_property(part,
"D:getetag");
4298 if(etag) {
4299 if(strlen(etag) >
2 && etag[
0] ==
'W' && etag[
1] ==
'/') {
4300 etag = etag +
2;
4301 }
4302
4303 fp->etag = strdup(etag);
4304 cxListAdd(updated_parts, fp);
4305 }
4306 }
else {
4307 uint64_t name_partnum =
0;
4308 char *res_name = part->name;
4309 while(res_name[
0] ==
'0' && res_name[
1] !=
'\0') {
4310 res_name++;
4311 }
4312 DavBool delete_part =
0;
4313 if(strlen(part->name) != digits) {
4314 delete_part =
1;
4315 }
else if(util_strtouint(res_name, &name_partnum)) {
4316 if(name_partnum >= blockindex) {
4317 delete_part =
1;
4318 }
4319 }
4320
4321 if(delete_part) {
4322 if(dav_delete(part)) {
4323 log_resource_error(part->session, part->path);
4324 }
4325 }
4326 }
4327 part = part->next;
4328 }
4329 dav_resource_free_all(parts);
4330
4331 cxMapDestroy(updated_parts_map);
4332
4333 *err =
0;
4334 return updated_parts;
4335 }
4336
4337 void update_parts(LocalResource *local, CxList *updates,
uint64_t numparts) {
4338 size_t old_num = local->numparts;
4339 if(old_num > numparts) {
4340
4341 for(
size_t i=numparts;i<old_num;i++) {
4342 FilePart p = local->parts[i];
4343 if(p.etag) {
4344 free(p.etag);
4345 }
4346 if(p.hash) {
4347 free(p.hash);
4348 }
4349 }
4350 }
4351 if(numparts != local->numparts) {
4352 local->parts = realloc(local->parts, numparts *
sizeof(FilePart));
4353 local->numparts = numparts;
4354 }
4355
4356 if(!updates) {
4357 return;
4358 }
4359
4360 CxIterator i = cxListIterator(updates);
4361 cx_foreach(FilePart *, p, i) {
4362 if(p->block > numparts) {
4363
4364
4365 continue;
4366 }
4367
4368 FilePart *old = &local->parts[p->block];
4369 if(p->block < old_num) {
4370
4371 if(old->hash) {
4372 free(old->hash);
4373 old->hash =
NULL;
4374 }
4375 if(old->etag) {
4376 free(old->etag);
4377 old->etag =
NULL;
4378 }
4379 }
4380 old->block = p->block;
4381 old->hash = p->hash;
4382 old->etag = p->etag;
4383 free(p);
4384 }
4385 }
4386
4387 int sync_put_resource(
4388 SyncDirectory *dir,
4389 DavResource *res,
4390 LocalResource *local,
4391 int *counter)
4392 {
4393 char *local_path = create_local_path(dir, local_resource_path(local));
4394
4395 SYS_STAT s;
4396 if(sys_stat(local_path, &s)) {
4397 log_error(
"Cannot stat file: %s: %s\n", local_path, strerror(errno));
4398 free(local_path);
4399 return -
1;
4400 }
4401
4402 DavBool islink = local->link_target ?
1 :
0;
4403 if(!local->link_target && local->link_updated) {
4404 dav_remove_property_ns(res,
DAV_PROPS_NS,
"link");
4405 }
4406
4407 size_t split_blocksize = resource_get_blocksize(dir, local, res, s.st_size);
4408
4409 FILE *in = sys_fopen(local_path,
"rb");
4410 if(!in) {
4411 log_error(
"Cannot open file %s: %s\n", local_path, strerror(errno));
4412 free(local_path);
4413 return -
1;
4414 }
4415
4416 DavBool issplit = split_blocksize ==
0 ?
FALSE :
TRUE;
4417 int split_err =
0;
4418 CxList *parts =
NULL;
4419 uint64_t blockcount =
0;
4420
4421 if(islink) {
4422 dav_set_string_property_ns(res,
DAV_PROPS_NS,
"link", local->link_target);
4423 }
else if(issplit) {
4424
4425 char blocksize_str[
32];
4426 snprintf(blocksize_str,
32,
"%zu", split_blocksize);
4427 dav_set_string_property_ns(res,
DAV_NS,
"split", blocksize_str);
4428
4429
4430 parts = upload_parts(
4431 local,
4432 res,
4433 in,
4434 s.st_size,
4435 split_blocksize,
4436 &blockcount,
4437 &split_err);
4438 }
else {
4439
4440 dav_set_content(res, in, (dav_read_func)myread, (dav_seek_func)file_seek);
4441 dav_set_content_length(res, s.st_size);
4442 }
4443 if(split_err) {
4444 free(local_path);
4445 return -
1;
4446 }
4447
4448 MetadataHashes hashes;
4449 hashes = sync_set_metadata_properties(dir, res->session, res, local,
FALSE);
4450
4451
4452
4453 int exists = res->exists;
4454 int vend_required =
0;
4455 if(dir->versioning && dir->versioning->always && !issplit) {
4456
4457
4458 if(exists && versioning_init(dir, local, res)) {
4459
4460 log_error(
"Cannot activate versioncontrol for resource: %s\n", res->href);
4461 free(local_path);
4462 return -
1;
4463 }
else {
4464 int err = versioning_begin(dir, res, &exists, &vend_required);
4465 if(err) {
4466 log_error(
"Cannot store version for resource: %s\n", res->href);
4467 free(local_path);
4468 return -
1;
4469 }
4470 }
4471 }
4472
4473 int ret = -
2;
4474 dir->max_retry =
2;
4475 for(
int i=
0;i<=dir->max_retry;i++) {
4476 if(!exists && dav_create(res)) {
4477 continue;
4478 }
4479 exists =
1;
4480 if(dav_store(res)) {
4481 continue;
4482 }
4483 ret =
0;
4484 break;
4485 }
4486
4487 if(vend_required) {
4488 if(versioning_end(dir, res)) {
4489 log_error(
"Cannot checkin resource\n");
4490 ret =
1;
4491 }
4492 }
4493
4494 if(ret ==
0) {
4495 (*counter)++;
4496
4497 local->tags_updated =
0;
4498
4499 update_metadata_hashes(local, hashes);
4500 update_parts(local, parts, blockcount);
4501
4502
4503 DavResource *up_res = dav_get(res->session, res->path,
"D:getetag,idav:status");
4504
4505 if(up_res) {
4506
4507 if(up_res->contentlength < s.st_size && !issplit && !islink) {
4508 log_error(
"Incomplete Upload: %s\n", local_path);
4509 ret = -
1;
4510
4511 sync_set_status(res,
"broken");
4512 }
else {
4513
4514 char *etag = dav_get_string_property(up_res,
"D:getetag");
4515 local_resource_set_etag(local, etag);
4516
4517 if(!issplit &&
SYNC_STORE_HASH(dir)) {
4518 if(local->hash) {
4519 free(local->hash);
4520 }
4521
4522 local->hash = util_file_hash(local_path);
4523 }
4524
4525 if(dav_get_string_property(up_res,
"idav:status")) {
4526 sync_remove_status(up_res);
4527 }
4528
4529 dav_resource_free(up_res);
4530 }
4531 }
4532 }
else {
4533 ret = -
1;
4534 sync_set_status(res,
"broken");
4535 }
4536
4537 fclose(in);
4538 free(local_path);
4539
4540 return ret;
4541 }
4542
4543 int sync_mkdir(SyncDirectory *dir, DavResource *res, LocalResource *local) {
4544 res->iscollection =
1;
4545 int ret = -
1;
4546 for(
int i=
0;i<=dir->max_retry;i++) {
4547 if(dav_create(res)) {
4548 continue;
4549 }
4550 ret =
0;
4551 break;
4552 }
4553 return ret;
4554 }
4555
4556 int sync_move_remote_resource(
4557 SyncDirectory *dir,
4558 SyncDatabase *db,
4559 DavResource *origin,
4560 LocalResource *local,
4561 DavBool copy,
4562 int *counter)
4563 {
4564 char *local_path = create_local_path(dir, local->path);
4565
4566 SYS_STAT s;
4567 if(sys_stat(local_path, &s)) {
4568 log_error(
"Cannot stat file: %s: %s\n", local_path, strerror(errno));
4569 free(local_path);
4570 return -
1;
4571 }
4572 free(local_path);
4573
4574 int result =
0;
4575 if(copy) {
4576 result = dav_copy_o(origin, local->path,
FALSE);
4577 }
else {
4578 result = dav_move_o(origin, local->path,
FALSE);
4579 }
4580
4581 if(result !=
0) {
4582 return result;
4583 }
4584
4585 LocalResource *local_origin = local->origin;
4586 if(!copy) {
4587 cxMapRemove(db->resources, cx_hash_key_str(local_origin->path));
4588 }
4589
4590
4591 DavResource *up_res = dav_resource_new(origin->session, local->path);
4592 if(!up_res) {
4593 return 1;
4594 }
4595
4596 sync_set_metadata_from_stat(local, &s);
4597 MetadataHashes hashes;
4598 hashes = sync_set_metadata_properties(dir, up_res->session, up_res, local,
TRUE);
4599 if(dav_store(up_res)) {
4600 log_error(
"Error: cannot store resource metadata\n");
4601 }
4602
4603
4604 DavPropName p;
4605 p.ns =
"DAV:";
4606 p.name =
"getetag";
4607 if(!dav_load_prop(up_res, &p,
1)) {
4608 (*counter)++;
4609
4610
4611 char *etag = dav_get_string_property(up_res,
"D:getetag");
4612 local_resource_set_etag(local, etag);
4613
4614 local->last_modified = s.st_mtime;
4615 }
else {
4616 result =
1;
4617 }
4618
4619 dav_resource_free(up_res);
4620 return result;
4621 }
4622
4623 int sync_delete_remote_resource(
4624 SyncDirectory *dir,
4625 DavSession *sn,
4626 LocalResource *local_res,
4627 int *counter,
4628 CxList *cols)
4629 {
4630 DavResource *res = dav_get(sn, local_res->path,
"D:getetag,idav:split");
4631 if(!res) {
4632 return sn->error ==
DAV_NOT_FOUND ?
0 :
1;
4633 }
4634
4635 int ret =
0;
4636 sn->error =
DAV_OK;
4637 if(res->iscollection) {
4638 DavXmlNode *split = dav_get_property_ns(res,
DAV_NS,
"split");
4639 if(cols) {
4640 cxListAdd(cols, local_res);
4641 }
else if(split || !res->children) {
4642 log_printf(
"delete: %s\n", res->path);
4643 if(dav_delete(res)) {
4644 ret =
1;
4645 log_error(
"Cannot delete collection %s\n", res->path);
4646 }
else {
4647 (*counter)++;
4648 }
4649 }
4650 }
else {
4651 char *etag = dav_get_string_property(res,
"D:getetag");
4652 if(etag) {
4653 if(strlen(etag) >
2 && etag[
0] ==
'W' && etag[
1] ==
'/') {
4654 etag = etag +
2;
4655 }
4656 }
4657
4658 if(!nullstrcmp(etag, local_res->etag)) {
4659
4660
4661 log_printf(
"delete: %s\n", res->path);
4662 int exists =
1;
4663 int vend_required =
0;
4664 if(dir->versioning && dir->versioning->always) {
4665 if(versioning_delete_begin(dir, res, &exists, &vend_required)) {
4666 log_error(
"Cannot save resource version before deletion\n");
4667 ret =
1;
4668 }
4669 }
4670
4671 if(!ret && dav_delete(res) && exists) {
4672 if(sn->error !=
DAV_NOT_FOUND) {
4673 log_error(
"Cannot delete resource %s\n", res->path);
4674 ret =
1;
4675 }
4676 }
else {
4677 (*counter)++;
4678 }
4679
4680 if(vend_required) {
4681 versioning_delete_end(dir, res);
4682 }
4683 }
4684
4685
4686 }
4687
4688
4689 dav_resource_free(res);
4690
4691 return ret;
4692 }
4693
4694 MetadataHashes sync_set_metadata_properties(
4695 SyncDirectory *dir,
4696 DavSession *sn,
4697 DavResource *res,
4698 LocalResource *local,
4699 DavBool force)
4700 {
4701 if(force) {
4702 local->tags_updated =
1;
4703 local->finfo_updated =
1;
4704 local->xattr_updated =
1;
4705 }
4706
4707 MetadataHashes hashes = {
NULL,
NULL,
NULL,
0,
0,
0};
4708 if(dir->tagconfig) {
4709
4710 DavBool changed =
0;
4711 char *tags_hash =
NULL;
4712 CxList *tags = sync_get_file_tags(dir, local, &changed, &tags_hash);
4713 char *new_remote_hash = nullstrdup(tags_hash);
4714 if(changed || local->tags_updated) {
4715 DavBool store_tags =
TRUE;
4716
4717
4718 DavPropName p;
4719 p.ns =
DAV_PROPS_NS;
4720 p.name =
"tags";
4721 if(dav_load_prop(res, &p,
1) && sn->error !=
DAV_NOT_FOUND) {
4722 log_resource_error(sn, res->path);
4723 }
4724 CxList *remote_tags =
NULL;
4725 DavXmlNode *tagsprop = dav_get_property_ns(res,
DAV_PROPS_NS,
"tags");
4726 if(tagsprop) {
4727 remote_tags = parse_dav_xml_taglist(tagsprop);
4728 }
4729 char *remote_hash = create_tags_hash(remote_tags);
4730
4731 if(nullstrcmp(remote_hash, local->remote_tags_hash)) {
4732
4733 int conflict_resolution = force ?
TAG_NO_CONFLICT : dir->tagconfig->conflict;
4734 switch(conflict_resolution) {
4735 case TAG_NO_CONFLICT:
break;
4736 case TAG_KEEP_LOCAL:
break;
4737 case TAG_KEEP_REMOTE: {
4738 store_tags =
FALSE;
4739 local->tags_updated =
FALSE;
4740 break;
4741 }
4742 case TAG_MERGE: {
4743 CxList *new_tags = merge_tags(tags, remote_tags);
4744 free_taglist(tags);
4745 tags = new_tags;
4746
4747 nullfree(tags_hash);
4748 nullfree(new_remote_hash);
4749 tags_hash = create_tags_hash(tags);
4750 new_remote_hash = nullstrdup(tags_hash);
4751
4752 break;
4753 }
4754 }
4755 }
4756 nullfree(remote_hash);
4757
4758 if(dir->tagconfig->local_format ==
TAG_FORMAT_CSV) {
4759
4760
4761 add_tag_colors(tags, remote_tags);
4762 }
4763
4764 if(store_tags) {
4765 if(tags) {
4766 DavXmlNode *tagprop = create_xml_taglist(tags);
4767 dav_set_property_ns(res,
DAV_PROPS_NS,
"tags", tagprop);
4768 }
else {
4769 dav_remove_property_ns(res,
DAV_PROPS_NS,
"tags");
4770 }
4771
4772 hashes.tags = tags_hash;
4773 hashes.update_tags =
1;
4774 hashes.tags_remote = new_remote_hash;
4775 hashes.update_tags_remote =
1;
4776 }
4777
4778 free_taglist(remote_tags);
4779 }
else {
4780 if(tags_hash) {
4781 free(tags_hash);
4782 }
4783 }
4784 free_taglist(tags);
4785 }
4786
4787 if(local->finfo_updated) {
4788 struct stat s;
4789 s.st_mode = local->mode;
4790 s.st_mtime = local->last_modified;
4791 s.st_uid = local->uid;
4792 s.st_gid = local->gid;
4793 resource_set_finfo_s(&s, res, dir->metadata);
4794 }
4795
4796 if(local->xattr_updated) {
4797 if(local->xattr) {
4798 resource_set_xattr(res, local->xattr);
4799 hashes.xattr = local->xattr ? strdup(local->xattr->hash) :
NULL;
4800 hashes.update_xattr =
1;
4801 }
else {
4802 dav_remove_property(res,
"idavprops:xattributes");
4803 if(local->xattr_hash) {
4804 free(local->xattr_hash);
4805 local->xattr_hash =
NULL;
4806 }
4807 }
4808 }
4809
4810 local->tags_updated =
0;
4811
4812 return hashes;
4813 }
4814
4815 int sync_update_metadata(
4816 SyncDirectory *dir,
4817 DavSession *sn,
4818 DavResource *res,
4819 LocalResource *local)
4820 {
4821 MetadataHashes hashes = sync_set_metadata_properties(dir, sn, res, local,
FALSE);
4822
4823 int err =
0;
4824 if(dav_store(res)) {
4825 log_resource_error(sn, local->path);
4826 err =
1;
4827 }
else {
4828 update_metadata_hashes(local, hashes);
4829 local->tags_updated =
0;
4830 }
4831
4832 return err;
4833 }
4834
4835 void remove_deleted_conflicts(SyncDirectory *dir, SyncDatabase *db) {
4836 char **dc = calloc(
sizeof(
void*), db->conflict->size);
4837 int numdc =
0;
4838
4839 CxIterator i = cxMapIteratorValues(db->conflict);
4840 cx_foreach(LocalResource *, res, i) {
4841 char *path = create_local_path(dir, res->path);
4842 SYS_STAT s;
4843 if(sys_stat(path, &s)) {
4844 if(errno ==
ENOENT) {
4845 dc[numdc] = res->path;
4846 numdc++;
4847 }
else {
4848 log_error(
"Cannot stat file: %s: %s\n", path, strerror(errno));
4849 }
4850 }
4851 free(path);
4852 }
4853
4854 for(
int i=
0;i<numdc;i++) {
4855 cxMapRemove(db->conflict, cx_hash_key_str(dc[i]));
4856 }
4857
4858 free(dc);
4859 }
4860
4861 static void resolve_skipped(SyncDatabase *db) {
4862 CxIterator i = cxMapIteratorValues(db->resources);
4863 int skipped =
0;
4864 cx_foreach(LocalResource *, res, i) {
4865 if(res->skipped) {
4866 skipped++;
4867 log_error(
"skipped from push: %s\n", res->path);
4868 }
4869 }
4870 if(skipped >
0) {
4871 log_error(
4872 " To resolve conflict resources skipped by push run dav-sync pull first\n"
4873 " before resolve-conflicts or delete-conflicts.\n\n");
4874 }
4875 }
4876
4877 int cmd_resolve_conflicts(CmdArgs *a) {
4878 if(a->argc !=
1) {
4879 log_error(
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
4880 return -
1;
4881 }
4882
4883 SyncDirectory *dir = scfg_get_dir(a->argv[
0]);
4884 if(!dir) {
4885 log_error(
"Unknown sync dir: %s\n", a->argv[
0]);
4886 return -
1;
4887 }
4888 if(scfg_check_dir(dir)) {
4889 return -
1;
4890 }
4891 if(logfile_open(dir)) {
4892 return -
1;
4893 }
4894
4895 SyncDatabase *db = load_db(dir->database);
4896 if(!db) {
4897 log_error(
"Cannot load database file: %s\n", dir->database);
4898 return -
1;
4899 }
4900
4901 resolve_skipped(db);
4902
4903 int ret =
0;
4904
4905
4906 int num_conflict = db->conflict->size;
4907
4908 cxMapClear(db->conflict);
4909
4910
4911 if(store_db(db, dir->database, dir->db_settings)) {
4912 log_error(
"Cannot store sync db\n");
4913 log_error(
"Abort\n");
4914 ret = -
2;
4915 }
4916
4917
4918 destroy_db(db);
4919
4920
4921 if(ret != -
2) {
4922 char *str_conflict = num_conflict ==
1 ?
"conflict" :
"conflicts";
4923 log_printf(
"Result: %d %s resolved\n", num_conflict, str_conflict);
4924 }
4925
4926 return ret;
4927 }
4928
4929 int cmd_delete_conflicts(CmdArgs *a) {
4930 if(a->argc !=
1) {
4931 fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
4932 return -
1;
4933 }
4934
4935 SyncDirectory *dir = scfg_get_dir(a->argv[
0]);
4936 if(!dir) {
4937 fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
4938 return -
1;
4939 }
4940 if(scfg_check_dir(dir)) {
4941 return -
1;
4942 }
4943 if(logfile_open(dir)) {
4944 return -
1;
4945 }
4946
4947 SyncDatabase *db = load_db(dir->database);
4948 if(!db) {
4949 log_error(
"Cannot load database file: %s\n", dir->database);
4950 return -
1;
4951 }
4952
4953 resolve_skipped(db);
4954
4955 int num_del =
0;
4956 int num_err =
0;
4957
4958 int ret =
0;
4959
4960
4961 CxIterator i = cxMapIteratorValues(db->conflict);
4962 cx_foreach(LocalResource*, res, i) {
4963 log_printf(
"delete: %s\n", res->path);
4964 char *path = create_local_path(dir, res->path);
4965 if(sys_unlink(path)) {
4966 if(errno !=
ENOENT) {
4967 log_error(
"unlink: %s", strerror(errno));
4968 num_err++;
4969 }
4970 }
else {
4971 num_del++;
4972 }
4973 free(path);
4974 }
4975
4976 cxMapClear(db->conflict);
4977
4978
4979 if(store_db(db, dir->database, dir->db_settings)) {
4980 log_error(
"Cannot store sync db\n");
4981 log_error(
"Abort\n");
4982 ret = -
1;
4983 }
4984
4985
4986 destroy_db(db);
4987
4988
4989 if(ret ==
0) {
4990 char *str_delete = num_del ==
1 ?
"file" :
"files";
4991 char *str_error = num_err ==
1 ?
"error" :
"errors";
4992 log_printf(
"Result: %d conflict %s deleted, %d %s\n",
4993 num_del, str_delete,
4994 num_err, str_error);
4995 }
4996
4997 return ret;
4998 }
4999
5000 int cmd_list_conflicts(CmdArgs *a) {
5001 if(a->argc !=
1) {
5002 fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
5003 return -
1;
5004 }
5005
5006 SyncDirectory *dir = scfg_get_dir(a->argv[
0]);
5007 if(!dir) {
5008 fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
5009 return -
1;
5010 }
5011 if(scfg_check_dir(dir)) {
5012 return -
1;
5013 }
5014
5015 SyncDatabase *db = load_db(dir->database);
5016 if(!db) {
5017 fprintf(stderr,
"Cannot load database file: %s\n", dir->database);
5018 return -
1;
5019 }
5020
5021 remove_deleted_conflicts(dir, db);
5022
5023
5024 CxIterator i = cxMapIteratorValues(db->conflict);
5025 CxList* conflict_sources = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
5026 cx_foreach(LocalResource *, res, i) {
5027 cxListAdd(conflict_sources, res->conflict_source);
5028 }
5029
5030
5031
5032 conflict_sources->cmpfunc = (cx_compare_func)strcmp;
5033 cxListSort(conflict_sources);
5034 i = cxListIterator(conflict_sources);
5035 char *prev =
"";
5036 cx_foreach(
char *, path, i) {
5037
5038
5039 if(!strcmp(path, prev)) {
5040 continue;
5041 }
5042
5043 log_printf(
"%s\n", path);
5044
5045 prev = path;
5046 }
5047
5048
5049 destroy_db(db);
5050
5051 return 0;
5052 }
5053
5054
5055
5056 static char* size_str(
uint64_t size) {
5057 char *str = malloc(
16);
5058
5059 if(size < 0x400) {
5060 snprintf(str,
16,
"%" PRIu64
" bytes", size);
5061 }
else if(size < 0x100000) {
5062 float s = (
float)size/0x400;
5063 int diff = (s*
100 - (
int)s*
100);
5064 if(diff >
90) {
5065 diff =
0;
5066 s +=
0.10f;
5067 }
5068 if(size < 0x2800 && diff !=
0) {
5069
5070 snprintf(str,
16,
"%.1f KiB", s);
5071 }
else {
5072 snprintf(str,
16,
"%.0f KiB", s);
5073 }
5074 }
else if(size < 0x40000000) {
5075 float s = (
float)size/0x100000;
5076 int diff = (s*
100 - (
int)s*
100);
5077 if(diff >
90) {
5078 diff =
0;
5079 s +=
0.10f;
5080 }
5081 if(size < 0xa00000 && diff !=
0) {
5082
5083 snprintf(str,
16,
"%.1f MiB", s);
5084 }
else {
5085 size /= 0x100000;
5086 snprintf(str,
16,
"%.0f MiB", s);
5087 }
5088 }
else if(size < 0x1000000000ULL) {
5089 float s = (
float)size/0x40000000;
5090 int diff = (s*
100 - (
int)s*
100);
5091 if(diff >
90) {
5092 diff =
0;
5093 s +=
0.10f;
5094 }
5095 if(size < 0x280000000 && diff !=
0) {
5096
5097 snprintf(str,
16,
"%.1f GiB", s);
5098 }
else {
5099 size /= 0x40000000;
5100 snprintf(str,
16,
"%.0f GiB", s);
5101 }
5102 }
else {
5103 size /=
1024;
5104 float s = (
float)size/0x40000000;
5105 int diff = (s*
100 - (
int)s*
100);
5106 if(diff >
90) {
5107 diff =
0;
5108 s +=
0.10f;
5109 }
5110 if(size < 0x280000000 && diff !=
0) {
5111
5112 snprintf(str,
16,
"%.1f TiB", s);
5113 }
else {
5114 size /= 0x40000000;
5115 snprintf(str,
16,
"%.0f TiB", s);
5116 }
5117 }
5118 return str;
5119 }
5120
5121 void print_resource_version(DavResource *res,
char *name) {
5122 time_t now = res->lastmodified;
5123 struct tm *date = gmtime(&now);
5124 char str[
32];
5125 putenv(
"LC_TIME=C");
5126 size_t len = strftime(str,
32,
"%a, %d %b %Y %H:%M:%S GMT", date);
5127
5128 log_printf(
"name: %s\n", name);
5129 log_printf(
"lastmodified: %s\n", str);
5130 char *server = util_url_base(res->session->base_url);
5131 char *url = util_concat_path(server, res->href);
5132 log_printf(
"url: %s\n", url);
5133 free(server);
5134 free(url);
5135 }
5136
5137 int cmd_list_versions(CmdArgs *a) {
5138 if(a->argc !=
1) {
5139 fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
5140 return -
1;
5141 }
5142
5143 SyncFile file;
5144 int ret =
0;
5145 char *path = a->argv[
0];
5146
5147 int err = sync_get_file(a, path, &file,
TRUE);
5148 if(err) {
5149 sync_print_get_file_err(path, err);
5150 return 1;
5151 }
5152 SyncDirectory *dir = file.dir;
5153
5154 if(!dir->versioning) {
5155 fprintf(stderr,
"No versioning configured for syncdir %s\n", dir->name);
5156 }
5157
5158 DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(dir->repository));
5159 if(!repo) {
5160 fprintf(stderr,
"Unknown repository %s\n", dir->repository);
5161 return -
1;
5162 }
5163
5164 SyncDatabase *db = load_db(dir->database);
5165 if(!db) {
5166 fprintf(stderr,
"Cannot load database file: %s\n", dir->database);
5167 return -
1;
5168 }
5169 remove_deleted_conflicts(dir, db);
5170
5171 DavSession *sn = create_session(a, ctx, repo, dir->collection);
5172 cxMempoolRegister(sn->mp, db, (cx_destructor_func)destroy_db);
5173 if (cmd_getoption(a,
"verbose")) {
5174 curl_easy_setopt(sn->handle,
CURLOPT_VERBOSE,
1L);
5175 curl_easy_setopt(sn->handle,
CURLOPT_STDERR, stderr);
5176 }
5177
5178 DavResource *res = dav_resource_new(sn, file.path);
5179 if(dir->versioning->type ==
VERSIONING_SIMPLE) {
5180 do {
5181 DavPropName p;
5182 p.ns =
DAV_NS;
5183 p.name =
VERSION_PATH_PROPERTY;
5184 if(dav_load_prop(res, &p,
1)) {
5185 print_resource_error(sn, file.path);
5186 ret =
1;
5187 break;
5188 }
5189 char *vcol_href = dav_get_string_property_ns(res,
DAV_NS,
VERSION_PATH_PROPERTY);
5190 if(!vcol_href) {
5191 ret =
1;
5192 break;
5193 }
5194
5195 DavResource *vcol = dav_resource_new_href(sn, vcol_href);
5196 if(!vcol) {
5197 ret =
1;
5198 break;
5199 }
5200
5201 if(dav_load_prop(vcol,
NULL,
0)) {
5202 print_resource_error(sn, vcol->path);
5203 ret =
1;
5204 break;
5205 }
5206
5207 DavResource *child = vcol->children;
5208 CxList *children = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)strcmp,
CX_STORE_POINTERS);
5209 while(child) {
5210 cxListAdd(children, child);
5211 child = child->next;
5212 }
5213 cxListSort(children);
5214
5215 DavBool first =
1;
5216 CxIterator i = cxListIterator(children);
5217 cx_foreach(DavResource *, c, i) {
5218 if(!first) {
5219 putchar(
'\n');
5220 }
5221 print_resource_version(c, c->name);
5222 first =
0;
5223 }
5224 cxListDestroy(children);
5225 }
while(
0);
5226 }
else if(dir->versioning->type ==
VERSIONING_DELTAV) {
5227 DavResource *versions = dav_versiontree(res,
NULL);
5228 DavResource *v = versions;
5229 DavBool first =
1;
5230 while(v) {
5231 if(!first) {
5232 putchar(
'\n');
5233 }
5234 char *vname = dav_get_string_property(v,
"D:version-name");
5235 print_resource_version(v, vname);
5236 first =
0;
5237 v = v->next;
5238 }
5239 }
5240
5241 free(file.path);
5242 dav_session_destroy(sn);
5243
5244 return ret;
5245 }
5246
5247
5248 int cmd_trash_info(CmdArgs *a) {
5249 if(a->argc !=
1) {
5250 fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
5251 return -
1;
5252 }
5253
5254 SyncDirectory *syncdir = scfg_get_dir(a->argv[
0]);
5255 if(!syncdir) {
5256 fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
5257 return -
1;
5258 }
5259 if(scfg_check_dir(syncdir)) {
5260 return -
1;
5261 }
5262
5263 if(!syncdir->trash) {
5264 log_printf(
"trash not configured for %s\n", syncdir->name);
5265 return 0;
5266 }
5267
5268 SYS_DIR dir = sys_opendir(syncdir->trash);
5269 if(!dir) {
5270 fprintf(stderr,
"cannot open trash directory: %s\n", syncdir->trash);
5271 perror(
"opendir");
5272 return -
1;
5273 }
5274
5275 uint64_t trashsize =
0;
5276 int count =
0;
5277 SysDirEnt *ent;
5278 while((ent = sys_readdir(dir)) !=
NULL) {
5279 if(!strcmp(ent->name,
".") || !strcmp(ent->name,
"..")) {
5280 continue;
5281 }
5282
5283 char *path = util_concat_path(syncdir->trash, ent->name);
5284
5285 SYS_STAT s;
5286 if(sys_stat(path, &s)) {
5287 perror(
"stat");
5288 }
else {
5289 trashsize += s.st_size;
5290 }
5291 count++;
5292
5293 free(path);
5294 }
5295 sys_closedir(dir);
5296
5297 log_printf(
"path: %s\n", syncdir->trash);
5298 log_printf(
"%d %s\n", count, count ==
1 ?
"file" :
"files");
5299 char *sizestr = size_str(trashsize);
5300 log_printf(
"%s\n", sizestr);
5301 free(sizestr);
5302
5303 return 0;
5304 }
5305
5306
5307 int cmd_empty_trash(CmdArgs *a) {
5308 if(a->argc !=
1) {
5309 fprintf(stderr,
"Too %s arguments\n", a->argc <
1 ?
"few" :
"many");
5310 return -
1;
5311 }
5312
5313 SyncDirectory *syncdir = scfg_get_dir(a->argv[
0]);
5314 if(!syncdir) {
5315 fprintf(stderr,
"Unknown sync dir: %s\n", a->argv[
0]);
5316 return -
1;
5317 }
5318 if(logfile_open(syncdir)) {
5319 return -
1;
5320 }
5321
5322 if(!syncdir->trash) {
5323 log_error(
"trash not configured for %s\n", syncdir->name);
5324 return -
1;
5325 }
5326
5327 SYS_DIR dir = sys_opendir(syncdir->trash);
5328 if(!dir) {
5329 log_error(
"cannot open trash directory: %s\n", syncdir->trash);
5330 log_error(
"opendir: %s\n", strerror(errno));
5331 return -
1;
5332 }
5333
5334 SysDirEnt *ent;
5335 while((ent = sys_readdir(dir)) !=
NULL) {
5336 if(!strcmp(ent->name,
".") || !strcmp(ent->name,
"..")) {
5337 continue;
5338 }
5339
5340 char *path = util_concat_path(syncdir->trash, ent->name);
5341 log_printf(
"delete: %s\n", path);
5342
5343 SYS_STAT s;
5344 if(sys_stat(path, &s)) {
5345 log_error(
"stat: %s\n", strerror(errno));
5346 free(path);
5347 continue;
5348 }
5349 if(
S_ISDIR(s.st_mode)) {
5350 if(rmdir(path)) {
5351 log_error(
"rmdir: %s\n", strerror(errno));
5352 }
5353 }
else {
5354 if(sys_unlink(path)) {
5355 log_error(
"unlink: %s\n", strerror(errno));
5356 }
5357 }
5358
5359 free(path);
5360 }
5361 sys_closedir(dir);
5362
5363 return 0;
5364 }
5365
5366 #define CMD_TAG_ADD 0
5367 #define CMD_TAG_REMOVE 1
5368 #define CMD_TAG_SET 2
5369 #define CMD_TAG_LIST 3
5370 int cmd_add_tag(CmdArgs *args) {
5371 if(args->argc !=
2) {
5372 fprintf(stderr,
"Too %s arguments\n", args->argc <=
1 ?
"few" :
"many");
5373 return -
1;
5374 }
5375 return cmd_tagop(args,
CMD_TAG_ADD);
5376 }
5377
5378 int cmd_remove_tag(CmdArgs *args) {
5379 if(args->argc !=
2) {
5380 fprintf(stderr,
"Too %s arguments\n", args->argc <=
1 ?
"few" :
"many");
5381 return -
1;
5382 }
5383 return cmd_tagop(args,
CMD_TAG_REMOVE);
5384 }
5385
5386 int cmd_set_tags(CmdArgs *args) {
5387 if(args->argc <
1 || args->argc >
2) {
5388 fprintf(stderr,
"Too %s arguments\n", args->argc <
1 ?
"few" :
"many");
5389 return -
1;
5390 }
5391 return cmd_tagop(args,
CMD_TAG_SET);
5392 }
5393
5394 int cmd_list_tags(CmdArgs *args) {
5395 if(args->argc !=
1) {
5396 fprintf(stderr,
"Too %s arguments\n", args->argc <=
1 ?
"few" :
"many");
5397 return -
1;
5398 }
5399 return cmd_tagop(args,
CMD_TAG_LIST);
5400 }
5401
5402 int cmd_tagop(CmdArgs *args,
int cmd) {
5403 SyncFile file;
5404 int ret =
0;
5405 char *path = args->argv[
0];
5406
5407 int err = sync_get_file(args, path, &file,
TRUE);
5408 if(err) {
5409 sync_print_get_file_err(path, err);
5410 return -
1;
5411 }
5412
5413 if(!file.dir->tagconfig) {
5414 fprintf(stderr,
"Tags are not supported for this sync directory\n");
5415 return -
1;
5416 }
5417
5418 SyncDatabase *db = load_db(file.dir->database);
5419 if(!db) {
5420 fprintf(stderr,
"Cannot load sync directory database\n");
5421 return -
1;
5422 }
5423
5424 LocalResource *newres =
NULL;
5425 LocalResource *localres = cxMapGet(db->resources, cx_hash_key_str(file.path));
5426 if(!localres) {
5427 newres = calloc(
1,
sizeof(LocalResource));
5428 newres->path = strdup(file.path);
5429 localres = newres;
5430 }
5431 CxList *tags =
NULL;
5432 DavBool store_tags =
FALSE;
5433
5434 if(cmd !=
CMD_TAG_SET) {
5435 char *tag = args->argv[
1];
5436 char *tagcolor =
NULL;
5437
5438 tags = sync_get_file_tags(file.dir, localres,
NULL,
NULL);
5439 CxIterator i = cxListIterator(tags ? tags : cxEmptyList);
5440 int x = -
1;
5441 cx_foreach(DavTag *, t, i) {
5442 if(cmd ==
CMD_TAG_LIST) {
5443 log_printf(
"%s\n", t->name);
5444 }
else if(!strcmp(t->name, tag)) {
5445 x = i.index;
5446 break;
5447 }
5448 }
5449
5450 if(cmd ==
CMD_TAG_ADD) {
5451 if(x <
0) {
5452 DavTag *newtag = malloc(
sizeof(DavTag));
5453 newtag->name = tag;
5454 newtag->color = tagcolor;
5455 if(!tags) {
5456 tags = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
5457 }
5458 cxListAdd(tags, newtag);
5459 store_tags =
TRUE;
5460 }
5461 }
else if(cmd ==
CMD_TAG_REMOVE) {
5462 if(x >=
0) {
5463 cxListRemove(tags, x);
5464 }
5465 store_tags =
TRUE;
5466 }
5467 }
else {
5468 if(args->argc ==
2) {
5469 char *tags_str = args->argv[
1];
5470 tags = parse_csv_taglist(tags_str, strlen(tags_str));
5471 store_tags =
TRUE;
5472
5473 }
else if (args->argc ==
1) {
5474 store_tags =
TRUE;
5475 }
else {
5476 fprintf(stderr,
"Too many arguments\n");
5477 ret = -
1;
5478 }
5479 }
5480
5481 if(store_tags) {
5482 if(sync_store_tags_local(file.dir,
NULL, path, tags)) {
5483 fprintf(stderr,
"Cannot store tags\n");
5484 }
5485 if(localres) {
5486 localres->tags_updated =
TRUE;
5487 if(!tags) {
5488 if(localres->tags_hash) {
5489 free(localres->tags_hash);
5490 localres->tags_hash =
NULL;
5491 }
5492 localres->tags_hash =
NULL;
5493 }
5494 }
5495 }
5496
5497 if(newres) {
5498 local_resource_free(newres);
5499 }
5500
5501
5502 if(store_db(db, file.dir->database, file.dir->db_settings)) {
5503 fprintf(stderr,
"Cannot store sync db\n");
5504 ret = -
2;
5505 }
5506
5507 free(file.path);
5508 return ret;
5509 }
5510
5511 int isfileindir(SyncDirectory *dir,
const char *path, SyncFile *f) {
5512 char *fullpath;
5513 if(path[
0] !=
'/') {
5514 size_t wdlen =
256;
5515 char *wd = malloc(wdlen);
5516 while(!getcwd(wd, wdlen)) {
5517 if(errno ==
ERANGE) {
5518 wdlen *=
2;
5519 char *newbuf = realloc(wd, wdlen);
5520 if (newbuf) {
5521 wd = newbuf;
5522 }
else {
5523 free(wd);
5524 return 0;
5525 }
5526 }
else {
5527 free(wd);
5528 return 0;
5529 }
5530 }
5531
5532 fullpath = util_concat_path(wd, path);
5533 free(wd);
5534 }
else {
5535 fullpath = strdup(path);
5536 }
5537
5538
5539 DavBool not_in_dir =
0;
5540
5541 cxstring fp = cx_str(fullpath);
5542 cxstring dp = cx_str(dir->path);
5543 if(fp.length == dp.length) {
5544 if(cx_strcmp(fp, dp)) {
5545 not_in_dir =
1;
5546 }
5547 }
else if(fp.length < dp.length) {
5548 not_in_dir =
1;
5549 }
else {
5550 if(!cx_strprefix(fp, dp)) {
5551 not_in_dir =
1;
5552 }
else {
5553 if(dp.ptr[dp.length-
1] ==
'/') {
5554 dp.length--;
5555 }
5556 if(fp.ptr[dp.length] !=
'/') {
5557 not_in_dir =
1;
5558 }
5559 }
5560 }
5561
5562 if(not_in_dir) {
5563 free(fullpath);
5564 return 0;
5565 }
5566
5567
5568
5569 f->dir = dir;
5570 f->path = util_concat_path(
"/", fullpath + strlen(dir->path));
5571
5572 free(fullpath);
5573 return 1;
5574 }
5575
5576 int sync_get_file(CmdArgs *args,
const char *path, SyncFile *f, DavBool dostat) {
5577 if(dostat) {
5578 SYS_STAT s;
5579 if(sys_stat(path, &s)) {
5580 switch(errno) {
5581 case EACCES:
return 2;
5582 case ENOENT:
return 1;
5583 default:
return 3;
5584 }
5585 }
5586 }
5587
5588 char *sdir = cmd_getoption(args,
"syncdir");
5589
5590 if(sdir) {
5591 SyncDirectory *dir = scfg_get_dir(sdir);
5592 if(!dir) {
5593 return 6;
5594 }
5595 if(!isfileindir(dir, path, f)) {
5596 return 4;
5597 }
5598 }
else {
5599 SyncDirectory *target =
NULL;
5600
5601 CxIterator i = scfg_directory_iterator();
5602 cx_foreach(SyncDirectory *, dir, i) {
5603 if(isfileindir(dir, path, f)) {
5604 if(target) {
5605 return 5;
5606 }
else {
5607 target = dir;
5608 }
5609 }
5610 }
5611
5612 if(!target) {
5613 return 4;
5614 }
5615 }
5616
5617 return 0;
5618 }
5619
5620 void sync_print_get_file_err(
const char *path,
int err) {
5621 switch(err) {
5622 case 1: log_error(
"File %s: not found\n", path);
break;
5623 case 2: log_error(
"File %s: permission denied\n", path);
break;
5624 case 3: log_error(
"File %s: stat failed: %s\n", path, strerror(errno));
break;
5625 case 4: log_error(
"File %s is not in any syncdir\n", path);
break;
5626 case 5: log_error(
"File %s is in multiple syncdirs\n", path);
break;
5627 case 6: log_error(
"Syncdir not found\n");
break;
5628 }
5629 }
5630
5631
5632 int cmd_add_directory(CmdArgs *args) {
5633 DavConfig *davconfig = get_config();
5634
5635 if(!davconfig->repositories) {
5636 fprintf(stderr,
"No repositories available. Run ''dav add-repository'' first.\n");
5637 fprintf(stderr,
"Abort\n");
5638 return -
1;
5639 }
5640
5641 log_printf(
"Each sync directory must have an unique name.\n");
5642 char *name = assistant_getcfg(
"name");
5643 if(!name) {
5644 fprintf(stderr,
"Abort\n");
5645 return -
1;
5646 }
5647 if(scfg_get_dir(name)) {
5648 fprintf(stderr,
"Directory %s already exists.\nAbort\n", name);
5649 return -
1;
5650 }
5651
5652 log_printf(
"Enter local directory path.\n");
5653 char *path = assistant_getcfg(
"path");
5654 if(!path) {
5655 fprintf(stderr,
"Abort\n");
5656 return -
1;
5657 }
5658
5659 log_printf(
"Specify webdav repository.\n");
5660 int i =
0;
5661 for (DavCfgRepository *r = davconfig->repositories; r !=
NULL; r = r->next) {
5662 log_printf(
"%d) %s\n", i, r->name.value.ptr);
5663 i++;
5664 }
5665 char *repository = assistant_getcfg(
"repository");
5666 char *reponame =
NULL;
5667 if(!repository) {
5668 fprintf(stderr,
"Abort\n");
5669 return -
1;
5670 }
5671 int64_t reponum =
0;
5672 if(util_strtoint(repository, &reponum)) {
5673 if(reponum <
0) {
5674 fprintf(stderr,
"Wrong input.\nAbort\n");
5675 return -
1;
5676 }
5677 DavCfgRepository *r = cx_linked_list_at(davconfig->repositories,
0,
5678 offsetof(DavCfgRepository, next),
5679 reponum);
5680 if(r !=
NULL) {
5681 reponame = r->name.value.ptr;
5682 }
else {
5683 fprintf(stderr,
"Wrong input.\nAbort\n");
5684 return -
1;
5685 }
5686 }
else {
5687 if(dav_config_get_repository(davconfig, cx_str(repository))) {
5688 reponame = repository;
5689 }
else {
5690 fprintf(stderr,
"Repository %s doesn''t exist.\nAbort\n", repository);
5691 return -
1;
5692 }
5693 }
5694
5695 log_printf(
"Enter collection relative to the repository base url.\n");
5696 char *collection = assistant_getdefcfg(
"collection",
"/");
5697
5698 char *db = generate_db_name(name);
5699
5700 SyncDirectory dir;
5701 memset(&dir,
0,
sizeof(SyncDirectory));
5702 dir.name = name;
5703 dir.path = path;
5704 dir.repository = reponame;
5705 dir.collection = collection;
5706 dir.trash =
".trash";
5707 dir.database = db;
5708
5709 int ret =
0;
5710 if(add_directory(&dir)) {
5711 fprintf(stderr,
"Cannot write sync.xml\n");
5712 ret = -
1;
5713 }
else {
5714 log_printf(
"\nAdded directory: %s (%s)\n", name, path);
5715 }
5716
5717 free(name);
5718 free(path);
5719 free(repository);
5720 free(collection);
5721 free(db);
5722
5723 return ret;
5724 }
5725
5726 int cmd_list_dirs() {
5727 CxIterator iter = scfg_directory_iterator();
5728 cx_foreach(SyncDirectory *, dir, iter) {
5729 log_printf(
"%s\n", dir->name);
5730 }
5731 return 0;
5732 }
5733
5734 int cmd_check_repositories(CmdArgs *a) {
5735 int ret =
EXIT_SUCCESS;
5736
5737 CxList *reponames = cxLinkedListCreateSimple(
CX_STORE_POINTERS);
5738 {
5739 CxIterator iter = scfg_directory_iterator();
5740 cx_foreach(SyncDirectory *, dir, iter) {
5741 cxListAdd(reponames, dir->repository);
5742 }
5743 }
5744
5745 CxIterator iter = cxListIterator(reponames);
5746 cx_foreach(
char *, reponame, iter) {
5747 log_printf(
"Checking %s... ", reponame);
5748 DavCfgRepository *repo = dav_config_get_repository(get_config(), cx_str(reponame));
5749 if (!repo) {
5750 log_printf(
" not found in config.xml!\n");
5751 ret =
EXIT_FAILURE;
5752 }
else {
5753 DavSession *sn = create_session(a, ctx, repo, repo->url.value.ptr);
5754 if (sn) {
5755 DavResource *res = dav_query(sn,
5756 "select - from / with depth = 0");
5757 if (res) {
5758 log_printf(
"OK.\n");
5759 dav_resource_free(res);
5760 }
else {
5761 log_printf(
"unavailable!\n");
5762 ret =
EXIT_FAILURE;
5763 }
5764 dav_session_destroy(sn);
5765 }
else {
5766 log_printf(
"cannot create session!\n");
5767 ret =
EXIT_FAILURE;
5768 }
5769 }
5770 }
5771
5772 cxListDestroy(reponames);
5773
5774 return ret;
5775 }
5776
5777 char* create_locktoken_file(
const char *syncdirname,
const char *locktoken) {
5778 cxmutstr fname = cx_asprintf(
"locktoken-%s.txt", syncdirname);
5779 char *path = config_file_path(fname.ptr);
5780 free(fname.ptr);
5781
5782 FILE *file = sys_fopen(path,
"w");
5783 if(file) {
5784 log_error(
"%s\n", locktoken);
5785 fclose(file);
5786 return path;
5787 }
else {
5788 log_error(
"Cannot create locktoken file: %s", strerror(errno));
5789 free(path);
5790 return NULL;
5791 }
5792 }
5793
5794 char* sync_get_content_hash(DavResource *res) {
5795 uint32_t flags = res->session->flags;
5796 if((flags &
DAV_SESSION_ENCRYPT_CONTENT) ==
DAV_SESSION_ENCRYPT_CONTENT) {
5797 char *enc_hash = dav_get_string_property_ns(res,
DAV_NS,
"crypto-hash");
5798 char *keyname = dav_get_string_property_ns(res,
DAV_NS,
"crypto-key");
5799 if(enc_hash && keyname) {
5800 DavKey *key = dav_context_get_key(res->session->context, keyname);
5801 if(!key) {
5802 return NULL;
5803 }
5804
5805 size_t len =
0;
5806 char *dec_hash = aes_decrypt(enc_hash, &len, key);
5807 if(!dec_hash) {
5808 return NULL;
5809 }
5810
5811 char *hex_hash = util_hexstr((
unsigned char*)dec_hash, len);
5812 free(dec_hash);
5813 return hex_hash;
5814 }
5815 }
else {
5816 char *hash = dav_get_string_property_ns(res,
DAV_NS,
"content-hash");
5817 if(hash) {
5818 return strdup(hash);
5819 }
5820 }
5821 return NULL;
5822 }
5823
5824 void sync_set_content_hash(DavResource *res,
const unsigned char *hashdata) {
5825 uint32_t flags = res->session->flags;
5826 if((flags &
DAV_SESSION_ENCRYPT_CONTENT) ==
DAV_SESSION_ENCRYPT_CONTENT) {
5827 if(res->session->key) {
5828 char *enc_hash = aes_encrypt((
const char*)hashdata,
DAV_SHA256_DIGEST_LENGTH, res->session->key);
5829 if(enc_hash) {
5830 dav_set_string_property_ns(res,
DAV_NS,
"crypto-hash", enc_hash);
5831 free(enc_hash);
5832 }
5833 }
5834 }
else {
5835 char *hex_hash = util_hexstr(hashdata,
DAV_SHA256_DIGEST_LENGTH);
5836 dav_set_string_property_ns(res,
DAV_NS,
"content-hash", hex_hash);
5837 free(hex_hash);
5838 }
5839 }
5840