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