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