UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2019 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 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 // unix includes 46 #include <unistd.h> 47 #include <utime.h> 48 #include <pthread.h> 49 #else 50 //windows includes 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 * strcmp version that works with NULL pointers 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 // ignore sigpipe to make sure the program doesn't exit 317 // if stdout will be closed (for example by using dav-sync ... | head) 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 // prepare signal handler thread 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 /* no output, the warnings are written by load_config */ 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 // cleanup 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); // block thread 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 // include/exclude filter 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 // trash filter 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 // versioning filter 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 // NOTE: currently not implementable 546 //int scope = res->iscollection ? 547 // DAV_SYNC_TAGFILTER_SCOPE_COLLECTION 548 // : DAV_SYNC_TAGFILTER_SCOPE_RESOURCE; 549 //if((tagfilter->scope & scope) != scope) { 550 // return 1; 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 //int scope = res->isdirectory ? 575 // DAV_SYNC_TAGFILTER_SCOPE_COLLECTION 576 // : DAV_SYNC_TAGFILTER_SCOPE_RESOURCE; 577 //if((tagfilter->scope & scope) != scope) { 578 // return 1; 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 // collection is NULL or "/" 615 // we don't need to find any collection because the repo url is 616 // the base url 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); // exec this to get the href 637 // we actually don't care what the result is 638 // if it doesn't exists, an error will occur later 639 // and we can't handle it here 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 // exclude tag xattr 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 // if there are syntax errors in the command line, fail asap. 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 // TODO: tons of memory leaks... 728 // call free_tagfilter() before each return 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 // lock repository 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 // TODO: free 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 // TODO: free 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); // type: MovedFile* 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); // type: LocalResource* 847 848 //UcxMap *svrres = ucx_map_new(db->resources->count); 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 // don't delete files filtered by config 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 // check if a resource has changed on the server 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; // never happens 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 // remove every server resource from dbres 925 // all remaining elements are the resources that are removed 926 // on the server 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 // find deleted resources 937 // svrres currently contains all resources from the server 938 // and will replace the current db->resources map later 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 // TODO: what is the meaning of this code? without overflow the condition is never true 948 // if(lres_removed->size > lres_removed->size * 2) { 949 // cxMapRehash(lres_removed); 950 // } 951 } 952 } 953 954 // 955 // BEGIN PULL 956 // 957 958 // the first thing we need are all directories to put the files in 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 // we need a map for all conflicts for fast lookups 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 // check for moved/copied files 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 // origin doesn't exist or is inaccessible 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 // remove item from res_new 1012 cxIteratorFlagRemoval(mut_iter); 1013 } 1014 } 1015 1016 // do copy/move operations 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 // move file 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 // download all new, modified and conflict files 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 // download the resource 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 // update metadata 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 // this should never happen but who knows 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 // sort dir list, we need to delete dirs with higher depth first 1122 cxListSort(rmdirs); 1123 // delete dirs 1124 i = cxListIterator(rmdirs); 1125 cx_foreach(LocalResource *, local_dir, i) { 1126 if(!sync_remove_local_directory(dir, local_dir)) { 1127 // dir successfully removed, now remove the related db entry 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 // unlock repository 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 // store db 1147 if(store_db(db, dir->database, dir->db_settings)) { 1148 fprintf(stderr, "Cannot store sync db\n"); 1149 ret = -2; 1150 } 1151 1152 // cleanup 1153 dav_session_destroy(sn); 1154 1155 if(!locked && locktokenfile) { 1156 remove(locktokenfile); 1157 } 1158 1159 // Report 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; // make sure isdirectory is set 1222 } else { 1223 // set change to REMOTE_CHANGE_MKDIR, which will fail later 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 // resource is already up-to-date on the client 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 // resource is already up-to-date on the client 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 // new file is a link 1268 ret = REMOTE_CHANGE_LINK; 1269 1270 if(exists && type == REMOTE_CHANGE_CONFLICT_LOCAL_MODIFIED) { 1271 // a file with the same name already exists 1272 // if it is a link, compare the targets 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 // if hashing is enabled we can compare the hash of the remote file 1294 // with the local file to test if a file is really modified 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 // because rehashing a file is slow, there is a config element for 1304 // disabling this (pull-skip-hashing) 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 // if local already exists, update the hash here 1313 // because it is possible that there are metadata updates 1314 // and in this case the db will updated later and needs 1315 // the current hash 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 // if a file is not modified, check if the metadata has changed 1329 while(ret == REMOTE_NO_CHANGE && local) { 1330 // check if tags have changed 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 // check if extended attributes have changed 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 // check if finfo has changed 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 // if update_db is set, a file was modified on the server, but we already 1376 // have the file content, but we need to update the db 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 // update local res 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 } // else: hash already updated 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 // new part 1454 download_part = TRUE; 1455 } else { 1456 FilePart p = local->parts[partnum]; // local is always non-null here 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 // if we don't download the block, we don't know the size 1502 // but it can't be bigger than the file 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 } // else: res is not a regular file part 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 // TODO: maybe we should not copy the whole resource 1585 // with all metadata hashes 1586 local = local_resource_copy(content, res->path); 1587 } else { 1588 // reuse previous LocalResource (content) 1589 // remove it from db->resources, change path and put it back 1590 local = cxMapRemoveAndGet(db->resources, cx_hash_key_str(content->path)); 1591 if(!local) { 1592 // can't happen, but handle it nevertheless 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 // dont free local->etag, because local_resource_set_etag will do that 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 // set metadata from stat 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 // why are we here? 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 // only true if issplit is true 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 // new local resource 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 // set metadata from stat 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 // TODO: use resource_local_path return value (necessary for creating links on windows) 1830 //char *res_path = resource_local_path(res); 1831 char *local_path = create_local_path(dir, res->path); 1832 //free(res_path); 1833 1834 log_printf("get: %s\n", res->path); 1835 // create directory 1836 // ignore error if it already exists 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 // stat for getting metadata 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 // if it doesn't exist in the db, create an entry for the dir 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 // cleanup LocalResource 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 // set metadata 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 // cleanup 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 // don't print error when dirs are not empty 1927 // because that can regulary happen, because the pull conflict 1928 // detection can prevent files from being deleted and in this case 1929 // the parent dir is not empty 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 //printf("errno: %d\n", errno); 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 //printf("errno: %d\n", errno); 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 // if there are syntax errors in the command line, fail asap. 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; // conflict detection 2142 2143 // lock repository 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 // upload all changed files 2184 //UcxList *resources = cmd_getoption(a, "read") ? 2185 // read_changes(dir, db) : local_scan(dir, db); 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 // ignore all files, that are excluded by a static filter (sync.xml) 2192 2193 // static include/exclude filter 2194 if(res_matches_dir_filter(dir, local_res->path+1)) { 2195 continue; 2196 } 2197 // static tag filter 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 // we need a fast file lookup map later to detect deleted files 2209 cxMapPut(resources_map, cx_hash_key_str(local_res->path), local_res); 2210 2211 // dynamic tag filter 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 // this makes sure the file will not be deleted later 2219 dbres->keep = TRUE; 2220 } 2221 } 2222 continue; 2223 } 2224 2225 // skip conflict backups silently 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 // calculate hashes of all new files and check if a file 2261 // was moved or is a copy 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 // check if a file with this hash already exists 2272 LocalResource *origin = cxMapGet(db_hashes, cx_hash_key_str(hash)); 2273 if(origin) { 2274 local->origin = local_resource_copy(origin, origin->path); 2275 // the file is a copied/moved file 2276 // check if the file is in the resources_map, because then 2277 // it still exists 2278 if(cxMapGet(resources_map, cx_hash_key_str(origin->path))) { 2279 cxListAdd(ls_copy, local); 2280 } else { 2281 cxListAdd(ls_move, local); 2282 // put file in resources_map to prevent deletion 2283 cxMapPut(resources_map, cx_hash_key_str(origin->path), local); 2284 } 2285 // remove list elemend from ls_new 2286 cxIteratorFlagRemoval(mut_iter); 2287 } 2288 2289 free(local_path); 2290 } 2291 } 2292 2293 // find all deleted files and cleanup the database 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 // all filtered files should be removed from the database 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 // The current LocalResource is in the database but doesn't exist 2313 // in the filesystem anymore. This means the file was deleted 2314 // and should be deleted on the server 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 // BEGIN PUSH 2352 // 2353 2354 int ret = 0; 2355 int error = 0; 2356 2357 // create collections 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 // create collection 2376 // TODO: show 405 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 // dav_exists() failed 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 // success 2396 if(local_res->metadata_updated) { 2397 sync_update_metadata(dir, sn, res, local_res); 2398 } 2399 2400 // remove old db entry (if it exists) 2401 // and add add new entry 2402 // TODO: free?? 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 // copy/move files 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 // upload with put 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 // upload changed files 2473 //ls_modified = ucx_list_concat(ls_new, ls_modified); 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 // update etag in db 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 // in case of a conflict, don't store the resource 2542 // in the db, if it is new 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 // metadata updates 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 // delete all removed files 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; // remember pointer for cleanup 2576 CxList *col_list = cols; 2577 CxList *deletelist = ls_delete; 2578 for(int i=0;i<2;i++) { 2579 // the first iteration deletes everything from ls_delete except 2580 // all collections, which are stored in cols 2581 // in the second run all collections will be deleted 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 //local_resource_free(dbres); 2599 } 2600 } 2601 cxListSort(cols); 2602 deletelist = cols; 2603 col_list = NULL; 2604 } 2605 2606 cxListDestroy(cols_del); 2607 2608 // unlock repository 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 // store db 2619 if(store_db(db, dir->database, dir->db_settings)) { 2620 log_error("Cannot store sync db\n"); 2621 ret = -2; 2622 } 2623 2624 // cleanup 2625 if(!locked && locktokenfile) { 2626 remove(locktokenfile); 2627 } 2628 2629 dav_session_destroy(sn); 2630 2631 // Report 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 // get all specified files and check the syncdir 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 // iterate over all db resources and check if any resource is 2732 // modified or deleted 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 // create DavSession 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 // lock repository 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 // TODO: old code sorted both modified and deleted, is this necessary? 2812 //UcxList *resources = ucx_list_concat(modified, deleted); 2813 //resources = ucx_list_sort(resources, (cmp_func)localres_cmp_path, NULL); 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 // By restoring an old version of a file, the local dir is not 2845 // in sync with the server anymore. Mark this file to change 2846 // the metadata later, to make sure, the file will be detected 2847 // as locally modified, on the next push/pull 2848 update_local_entry = FALSE; 2849 } else { 2850 vres = res; 2851 } 2852 2853 // download the resource 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 } // else should not happen 2874 } 2875 } 2876 } 2877 } 2878 iter = cxListIterator(deleted); 2879 } 2880 2881 // unlock repository 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 // store db 2892 if(store_db(db, dir->database, dir->db_settings)) { 2893 log_error("Cannot store sync db\n"); 2894 ret = -2; 2895 } 2896 2897 // cleanup 2898 dav_session_destroy(sn); 2899 2900 if(!locked && locktokenfile) { 2901 remove(locktokenfile); 2902 } 2903 2904 // Report 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 // get a directory path from the stack and read all entries 3040 // if an entry is a directory, put it on the stack 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 //res->path = strdup(path); 3100 res->size = s.st_size; 3101 } else { 3102 //res->path = util_concat_path(path, "/"); 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 // on Windows, we interpret .lnk files as links 3111 // we dont want resource names with this extension 3112 // and possibly sync this to other operating systems 3113 // therefore we remove the .lnk extension from the file name 3114 // change res->path 3115 // we only do this, if there isn't any other file with this name 3116 cxstring fpath = cx_str(file_path); 3117 cxstring rpath = cx_str(path); 3118 // remove last 4 chars (.lnk) 3119 cxmutstr new_file_path = cx_strdup(cx_strsubsl(fpath, 0 , fpath.length-4)); 3120 // check if a file with this name exists 3121 SYS_STAT nfp_s; 3122 if(!sys_stat(new_file_path.ptr, &nfp_s)) { 3123 // we can't upload the link without the file extension, because 3124 // there is another file with this name 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; // remove .lnk ext from resource path 3131 } 3132 free(new_file_path.ptr); 3133 #endif 3134 3135 if(lnkbuf) { 3136 // readlink successful 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 // the link points to a file inside the syncdir 3156 char *rel = util_create_relative_path(normalized, file_path); 3157 res->link_target = rel; 3158 } else { 3159 #ifndef SYS_LINK_EXT // if not windows 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 // can't access target, therefore we skip this link 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 // copy some metadata from db_res, that localscan does not deliver 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 // if the resource is splitted, copy the part info to the new 3235 // LocalResource obj, because we need it later 3236 if(db_res->parts) { 3237 local_resource_copy_parts(db_res, res); 3238 } 3239 3240 // check if the file must be restored on the server 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 // check if metadata has changed 3256 // metadata are tags, mode, owner, xattr 3257 // set res->metadata_updated to 1 in case any metadata has changed 3258 3259 // check if tags have changed 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; // tags removed 3277 } 3278 res->metadata_updated = res->tags_updated; 3279 } 3280 3281 // check if mtime has changed 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 // check if mode has changed 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 // check if owner has changed 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 // check if xattr have changed 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 // test if xattr are added, removed or changed 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 // check if the content of the file was modified 3321 // in case of links, just check if the link target has changed 3322 // for normal files, check last modified and size 3323 // or compare content hashes 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 // we already have hashes 3329 if(!strcmp(db_res->hash, res->hash)) { 3330 return 0; // hashes equal -> file content unchanged 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 // mtime and size unchanged, content also likely unchanged 3338 return 0; 3339 } else if(SYNC_HASHING(dir)) { 3340 // res->hash missing (see above) 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 // check if the content has really changed 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 // if requested, check if the local and remote res are equal 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 // the resource is on the server and the client has no etag 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 // something weird is happening, the server must support etags 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 // currently only xattr needed 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 // free old etag 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 // on Windows, add .lnk extension to links 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 // not a link 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 // file splitting disabled 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 // TODO: remove code dup (main.c: find_version) 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 // TODO: implement on windows 3629 #ifndef _WIN32 3630 // set mtime 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 // set mode 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 // create a map of all currently available local attributes 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 // use the xattr name as key and store any value 3685 cxMapPut(current_xattr, cx_hash_key_str(list[i]), list[i]); 3686 } 3687 } 3688 if(list) { 3689 free(list); 3690 } 3691 3692 // store extended attributes 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 // to detect which xattributes are removed, we remove all new 3705 // attributes from the map and all remaining attributes must 3706 // be removed with xattr_remove 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); // don't print error 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 // TODO: free tags and local_tags 3762 tags = new_tags; 3763 store_tags = TRUE; 3764 // make sure the merged tags will be pushed the next time 3765 local->tags_updated = TRUE; 3766 break; 3767 } 3768 } 3769 } else { 3770 if(!compare_taglists(tags, local_tags)) { 3771 store_tags = TRUE; 3772 } 3773 // TODO: free local_tags 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 // TODO: free stuff 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 //printf("update: %s\n", local->path); 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 //printf("update: %s\n", local->path); 3845 } 3846 // ignore errors on remove 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 // get the path to the version history collection for this resource 3980 // if propfind fails we just assume that it doesn't exist 3981 // better error handling is done later (sync_put_resource) 3982 // if there is no history collection for this resource, we create one 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 // create a new collection for version history 3990 // the name is a combination of a random prefix and a timestamp 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 // find a free url and move 'res' to this location 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 // tell the caller the resource does not exist anymore at 4042 // the original location 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 // we can just set the property here and don't need dav_store 4057 // because sync_put_resource will call dav_store(res) later 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 // DeltaV is so much easier :) 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 // versioning delete with DeltaV currently not supported in dav-sync 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 // this macro is only a workaround for a netbeans bug 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 // Make sure the resource is a collection. If it was a normal 4155 // resource until now, delete it and recreate it as collection 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 // this should never happen, but just make sure it doesn't crash 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 // calculate the maximal length of resource names 4191 // names should have all the same length and contain the block number 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 // temporarly disable name encryption, because we don't need it for 4209 // part names 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 // we don't have a hash for this block, therefore it must be new 4222 upload_block = 1; 4223 } else { 4224 FilePart part = local->parts[blockindex]; 4225 if(!strcmp(part.hash, block_hash)) { 4226 // no change 4227 free(block_hash); 4228 block_hash = NULL; 4229 } else { 4230 // block has changed 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 // upload part 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 // successfully uploaded part 4250 4251 // store the FilePart in a map 4252 // later we do a propfind and add the etag 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 // restore flags 4269 res->session->flags = session_flags; 4270 4271 free(buffer); 4272 if(*err) { 4273 cxMapDestroy(updated_parts_map); 4274 return NULL; 4275 } 4276 4277 // set content-hash 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 // get etags from uploaded resources 4284 // also delete everything, that is not part of the file 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 // every part we uploaded is in the map 4297 // if we get parts that are not in the map, someone else uploaded it 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 } // else { wtf is wrong with this resource } 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 // free old parts 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 // just make sure things don't explode in case some weird stuff 4366 // is going on 4367 continue; 4368 } 4369 4370 FilePart *old = &local->parts[p->block]; 4371 if(p->block < old_num) { 4372 // cleanup existing part 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 // set split property 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 // splitted/partial upload 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 // regular file upload 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 // before sync_put_resource, remote_resource_is_changed does a propfind 4454 // and sets res->exists 4455 int exists = res->exists; 4456 int vend_required = 0; 4457 if(dir->versioning && dir->versioning->always && !issplit) { 4458 // in case the file exists, we need to put the file under 4459 // versioncontrol (DeltaV only, does nothing with simple versioning) 4460 if(exists && versioning_init(dir, local, res)) { 4461 // init failed 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 // check contentlength and get new etag 4505 DavResource *up_res = dav_get(res->session, res->path, "D:getetag,idav:status"); 4506 4507 if(up_res) { 4508 // the new content length must be equal or greater than the file size 4509 if(up_res->contentlength < s.st_size && !issplit && !islink) { 4510 log_error("Incomplete Upload: %s\n", local_path); 4511 ret = -1; 4512 // try to set the resource status to 'broken' 4513 sync_set_status(res, "broken"); 4514 } else { 4515 // everything seems fine, we can update the local resource 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 // TODO: calculate hash on upload 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 // set resource metadata 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 // get new etag 4606 DavPropName p; 4607 p.ns = "DAV:"; 4608 p.name = "getetag"; 4609 if(!dav_load_prop(up_res, &p, 1)) { 4610 (*counter)++; 4611 4612 // everything seems fine, we can update the local resource 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 // local resource metadata == remote resource metadata 4662 // resource can be deleted 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 // else TODO: should we inform the user that the file was modified on 4687 // the server and delete was skipped? 4688 } 4689 4690 // cleanup 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 // get local tags 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 // get remote tags 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 // the tags have changed on the server 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 // csv tag lists don't have colors, so we have to add 4762 // the colors from the remote tag list 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 // remove conflicts 4908 size_t num_conflict = cxMapSize(db->conflict); 4909 //ucx_map_free_content(db->conflict, (ucx_destructor)local_resource_free); 4910 cxMapClear(db->conflict); 4911 4912 // store db 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 // cleanup 4920 destroy_db(db); 4921 4922 // Report 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 // delete all conflict files 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 //ucx_map_free_content(db->conflict, (ucx_destructor)local_resource_free); 4978 cxMapClear(db->conflict); 4979 4980 // store db 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 // cleanup 4988 destroy_db(db); 4989 4990 // Report 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 // get all conflict sources 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 // print unique conflict sources 5037 cxListSort(conflict_sources); 5038 i = cxListIterator(conflict_sources); 5039 char *prev = ""; 5040 cx_foreach(char *, path, i) { 5041 // TODO: implement verbose print if(cmd_getoption(a, "verbose")) 5042 // log_printf("%s (%d)\n", path, confl_count); 5043 if(!strcmp(path, prev)) { 5044 continue; 5045 } 5046 5047 log_printf("%s\n", path); 5048 5049 prev = path; 5050 } 5051 5052 // cleanup 5053 destroy_db(db); 5054 5055 return 0; 5056 } 5057 5058 5059 // TODO: remove code dup (main.c ls_size_str) 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 // size < 10 KiB 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 // size < 10 MiB 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 // size < 10 GiB 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 // size < 10 TiB 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; // TODO: get color 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 // TODO: read from stdin if tags_str is "-" 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 // store db 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 // TODO: normalize path 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 // TODO: check filter 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